parent
4ddec83217
commit
b333bbf534
48 changed files with 2553 additions and 5 deletions
@ -0,0 +1,3 @@ |
||||
__version__ = '0.1.6' |
||||
|
||||
default_app_config = 'djangocms_forms.apps.DjangoCMSFormsConfig' |
||||
@ -0,0 +1,263 @@ |
||||
import datetime |
||||
from functools import update_wrapper |
||||
|
||||
from django.contrib import admin, messages |
||||
from django.contrib.admin.helpers import AdminErrorList, AdminForm |
||||
from django.contrib.auth import get_permission_codename |
||||
from django.contrib.auth.admin import csrf_protect_m |
||||
from django.core.exceptions import PermissionDenied |
||||
from django.http import HttpResponse |
||||
from django.shortcuts import redirect |
||||
from django.template.defaultfilters import slugify, yesno |
||||
from django.template.response import TemplateResponse |
||||
from django.utils import timezone |
||||
from django.utils.encoding import force_text |
||||
from django.utils.safestring import mark_safe |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
from tablib import Dataset |
||||
|
||||
from .conf import settings |
||||
from .forms import SubmissionExportForm |
||||
from .models import Form, FormSubmission |
||||
|
||||
try: |
||||
from django.contrib.admin.utils import unquote |
||||
except ImportError: |
||||
from django.contrib.admin.util import unquote |
||||
|
||||
try: |
||||
from django.http import JsonResponse |
||||
except ImportError: |
||||
from .compat import JsonResponse |
||||
|
||||
|
||||
class FormFilter(admin.SimpleListFilter): |
||||
title = _('Forms') |
||||
parameter_name = 'form' |
||||
|
||||
def lookups(self, request, model_admin): |
||||
forms = Form.active_objects.all() |
||||
for obj in forms: |
||||
yield ( |
||||
str(obj.id), u'%s (%s)' % (obj.name, obj.submission_count) |
||||
) |
||||
|
||||
def queryset(self, request, queryset): |
||||
if self.value(): |
||||
return queryset.filter(plugin_id=self.value()) |
||||
|
||||
|
||||
class FormSubmissionAdmin(admin.ModelAdmin): |
||||
change_form_template = 'admin/djangocms_forms/formsubmission/change_form.html' |
||||
change_list_template = 'admin/djangocms_forms/formsubmission/change_list.html' |
||||
export_form_template = 'admin/djangocms_forms/formsubmission/export_form.html' |
||||
list_display = ('plugin', 'creation_date_display', 'created_by', 'ip', 'referrer', ) |
||||
list_filter = (FormFilter, ) |
||||
readonly_fields = ('creation_date_display', 'created_by', 'plugin', 'ip', 'referrer', ) |
||||
date_hierarchy = 'creation_date' |
||||
fieldsets = ( |
||||
(None, { |
||||
'fields': ('creation_date_display', 'created_by', 'ip', 'referrer', ) |
||||
}), |
||||
) |
||||
|
||||
class Media: |
||||
js = ( |
||||
'js/djangocms_forms/admin/jquery-form-export.js', |
||||
) |
||||
|
||||
def has_add_permission(self, request): |
||||
return False |
||||
|
||||
def has_export_permission(self, request): |
||||
opts = self.opts |
||||
codename = get_permission_codename('export', opts) |
||||
return request.user.has_perm('%s.%s' % (opts.app_label, codename)) |
||||
|
||||
def creation_date_display(self, obj): |
||||
return obj.creation_date.strftime(settings.DJANGOCMS_FORMS_DATETIME_FORMAT) |
||||
creation_date_display.short_description = _('Sent On') |
||||
|
||||
def get_queryset(self, request): |
||||
qs = super(FormSubmissionAdmin, self).get_queryset(request) |
||||
return qs.select_related('created_by', 'plugin', ) |
||||
|
||||
def get_urls(self): |
||||
""" |
||||
Add the export view to urls. |
||||
""" |
||||
urls = super(FormSubmissionAdmin, self).get_urls() |
||||
from django.conf.urls import patterns, url |
||||
|
||||
def wrap(view): |
||||
def wrapper(*args, **kwargs): |
||||
return self.admin_site.admin_view(view)(*args, **kwargs) |
||||
return update_wrapper(wrapper, view) |
||||
|
||||
info = self.model._meta.app_label, self.model._meta.model_name |
||||
|
||||
extra_urls = patterns( |
||||
'', |
||||
url(r'^export/$', wrap(self.export_view), name='%s_%s_export' % info), |
||||
) |
||||
return extra_urls + urls |
||||
|
||||
@csrf_protect_m |
||||
def changelist_view(self, request, extra_context=None): |
||||
context = extra_context or {} |
||||
context.update({ |
||||
'title': self.opts.verbose_name_plural.title(), |
||||
'has_export_permission': self.has_export_permission(request), |
||||
}) |
||||
return super(FormSubmissionAdmin, self).changelist_view( |
||||
request, extra_context=context) |
||||
|
||||
def change_view(self, request, object_id, form_url='', extra_context=None): |
||||
context = extra_context or {} |
||||
obj = self.get_object(request, unquote(object_id)) |
||||
|
||||
if obj: |
||||
context.update({ |
||||
'title': force_text(obj.plugin), |
||||
}) |
||||
|
||||
return super(FormSubmissionAdmin, self).change_view( |
||||
request, object_id, form_url=form_url, |
||||
extra_context=context) |
||||
|
||||
def export_view(self, request, form_url=''): |
||||
"""The 'export' admin view for this model.""" |
||||
|
||||
info = self.opts.app_label, self.opts.model_name |
||||
|
||||
if not self.has_export_permission(request): |
||||
raise PermissionDenied |
||||
|
||||
form = SubmissionExportForm(data=request.POST if request.method == 'POST' else None) |
||||
|
||||
if form.is_valid(): |
||||
data = form.cleaned_data |
||||
queryset = self.get_queryset(request) \ |
||||
.filter(plugin_id=data.get('form')) \ |
||||
.select_related('created_by', 'plugin', ) |
||||
|
||||
from_date, to_date = data.get('from_date'), data.get('to_date') |
||||
headers = data.get('headers', []) |
||||
|
||||
if from_date: |
||||
queryset = queryset.filter(creation_date__gte=from_date) |
||||
if to_date: |
||||
queryset = queryset.filter(creation_date__lt=to_date + datetime.timedelta(days=1)) |
||||
|
||||
if not queryset.exists(): |
||||
message = _('No matching %s found for the given criteria. ' |
||||
'Please try again.') % self.opts.verbose_name_plural |
||||
self.message_user(request, message, level=messages.WARNING) |
||||
if request.is_ajax(): |
||||
data = { |
||||
'reloadBrowser': True, |
||||
'submissionCount': 0, |
||||
} |
||||
return JsonResponse(data) |
||||
return redirect('admin:%s_%s_export' % info) |
||||
|
||||
latest_submission = queryset[:1].get() |
||||
dataset = Dataset(title=latest_submission.plugin.name) |
||||
|
||||
if not headers: |
||||
headers = [field['label'].strip() for field in latest_submission.form_data] |
||||
for submission in queryset: |
||||
for field in submission.form_data: |
||||
label = field['label'].strip() |
||||
if label not in headers: |
||||
headers.append(label) |
||||
|
||||
if request.is_ajax(): |
||||
data = { |
||||
'reloadBrowser': False, |
||||
'submissionCount': queryset.count(), |
||||
'availableHeaders': headers, |
||||
} |
||||
return JsonResponse(data) |
||||
|
||||
headers.extend(['Submitted on', 'Sender IP', 'Referrer URL']) |
||||
dataset.headers = headers |
||||
|
||||
def humanize(field): |
||||
value = field['value'] |
||||
field_type = field['type'] |
||||
|
||||
if value in (None, '', [], (), {}): |
||||
return None |
||||
|
||||
if field_type == 'checkbox': |
||||
value = yesno(bool(value), u'{0},{1}'.format(_('Yes'), _('No'))) |
||||
if field_type == 'checkbox_multiple': |
||||
value = ', '.join(list(value)) |
||||
return value |
||||
|
||||
for submission in queryset: |
||||
row = [None] * len(headers) |
||||
for field in submission.form_data: |
||||
label = field['label'].strip() |
||||
if label in headers: |
||||
row[headers.index(label)] = humanize(field) |
||||
row[-3] = submission.creation_date.strftime( |
||||
settings.DJANGOCMS_FORMS_DATETIME_FORMAT) |
||||
row[-2] = submission.ip |
||||
row[-1] = submission.referrer |
||||
dataset.append(row) |
||||
|
||||
mimetype = { |
||||
'xls': 'application/vnd.ms-excel', |
||||
'csv': 'text/csv', |
||||
'html': 'text/html', |
||||
'yaml': 'text/yaml', |
||||
'json': 'application/json', |
||||
} |
||||
|
||||
file_type = data.get('file_type', 'xls') |
||||
filename = settings.DJANGOCMS_FORMS_EXPORT_FILENAME.format( |
||||
form_name=slugify(latest_submission.plugin.name)) |
||||
filename = timezone.now().strftime(filename) |
||||
filename = '%s.%s' % (filename, file_type) |
||||
|
||||
response = HttpResponse( |
||||
getattr(dataset, file_type), { |
||||
'content_type': mimetype.get(file_type, 'application/octet-stream') |
||||
}) |
||||
|
||||
response['Content-Disposition'] = 'attachment; filename=%s' % filename |
||||
return response |
||||
|
||||
# Wrap in all admin layout |
||||
fieldsets = ((None, {'fields': form.fields.keys()}),) |
||||
adminform = AdminForm(form, fieldsets, {}, model_admin=self) |
||||
media = self.media + adminform.media |
||||
|
||||
context = { |
||||
'title': _('Export %s') % force_text(self.opts.verbose_name_plural), |
||||
'adminform': adminform, |
||||
'is_popup': '_popup' in request.REQUEST, |
||||
'media': mark_safe(media), |
||||
'errors': AdminErrorList(form, ()), |
||||
'app_label': self.opts.app_label, |
||||
} |
||||
return self.render_export_form(request, context, form_url) |
||||
|
||||
def render_export_form(self, request, context, form_url=''): |
||||
""" |
||||
Render the from submission export form. |
||||
""" |
||||
context.update({ |
||||
'has_change_permission': self.has_change_permission(request), |
||||
'form_url': mark_safe(form_url), |
||||
'opts': self.opts, |
||||
'add': True, |
||||
'save_on_top': self.save_on_top, |
||||
}) |
||||
|
||||
return TemplateResponse(request, self.export_form_template, context) |
||||
|
||||
|
||||
admin.site.register(FormSubmission, FormSubmissionAdmin) |
||||
@ -0,0 +1,7 @@ |
||||
from django.apps import AppConfig |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
|
||||
|
||||
class DjangoCMSFormsConfig(AppConfig): |
||||
name = 'djangocms_forms' |
||||
verbose_name = _('Forms') |
||||
@ -0,0 +1,117 @@ |
||||
from cms.plugin_base import CMSPluginBase |
||||
from cms.plugin_pool import plugin_pool |
||||
from django import forms |
||||
from django.contrib import admin |
||||
from django.db import models |
||||
from django.template.loader import select_template |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
|
||||
from .conf import settings |
||||
from .forms import FormBuilder, FormDefinitionAdminForm, FormFieldInlineForm |
||||
from .models import FormDefinition, FormField |
||||
|
||||
|
||||
class FormFieldInline(admin.StackedInline): |
||||
model = FormField |
||||
form = FormFieldInlineForm |
||||
extra = 0 |
||||
fieldsets = ( |
||||
(None, { |
||||
'fields': (('label', 'field_type', 'required'), |
||||
'initial', 'placeholder_text', 'help_text', |
||||
'choice_values', 'position', ) |
||||
}), |
||||
) |
||||
|
||||
formfield_overrides = { |
||||
models.TextField: { |
||||
'widget': forms.Textarea( |
||||
attrs={'rows': 4, 'cols': 50}) |
||||
}, |
||||
} |
||||
|
||||
class Media: |
||||
css = { |
||||
'all': ('css/djangocms_forms/admin/djangocms_forms.css',) |
||||
} |
||||
js = ( |
||||
'js/djangocms_forms/libs/jquery.min.js', |
||||
'js/djangocms_forms/libs/jquery-ui.min.js', |
||||
|
||||
'js/djangocms_forms/admin/jquery-inline-positioning.js', |
||||
'js/djangocms_forms/admin/jquery-inline-rename.js', |
||||
'js/djangocms_forms/admin/jquery-inline-collapsible.js', |
||||
'js/djangocms_forms/admin/jquery-inline-toggle-fields.js', |
||||
) |
||||
|
||||
|
||||
class FormPlugin(CMSPluginBase): |
||||
name = settings.DJANGOCMS_FORMS_PLUGIN_NAME |
||||
module = settings.DJANGOCMS_FORMS_PLUGIN_MODULE |
||||
model = FormDefinition |
||||
cache = False |
||||
form = FormDefinitionAdminForm |
||||
inlines = (FormFieldInline, ) |
||||
render_template = settings.DJANGOCMS_FORMS_DEFAULT_TEMPLATE |
||||
|
||||
def get_fieldsets(self, request, obj=None): |
||||
if settings.DJANGOCMS_FORMS_FIELDSETS: |
||||
return settings.DJANGOCMS_FORMS_FIELDSETS |
||||
|
||||
fieldsets = ( |
||||
(None, {'fields': ('name', )}), |
||||
|
||||
(None, { |
||||
'description': _('The <strong>Title</strong> and <strong>Description</strong> ' |
||||
'will display above the input fields and Submit button.'), |
||||
'fields': ('title', 'description', ) |
||||
}), |
||||
(None, { |
||||
'description': _('By default, the Submit Button will say <strong>Submit</strong>. ' |
||||
'You can change this to say whatever you want'), |
||||
'fields': ('submit_btn_txt', 'form_template', ) |
||||
}), |
||||
(None, { |
||||
'description': _('You can also change the message that appears after someone ' |
||||
'submits your form. ' |
||||
'By default, this says <strong>Thank you!</strong>, ' |
||||
'but you are welcome to change this text as well.'), |
||||
'fields': ('post_submit_msg', ) |
||||
}), |
||||
(None, { |
||||
'fields': ('success_redirect', ('page_redirect', 'external_redirect'), ), |
||||
}), |
||||
(None, { |
||||
'description': '<strong>Submission Settings</strong> — ' |
||||
'Choose storage options to capture form data. You can enter ' |
||||
'an email address to have the form submissions emailed to you or ' |
||||
'log all the form submissions to the database.', |
||||
'fields': ('email_to', 'email_from', 'email_subject', |
||||
'email_uploaded_files', 'save_data', ), |
||||
}), |
||||
) |
||||
return fieldsets |
||||
|
||||
def get_render_template(self, context, instance, placeholder): |
||||
# returns the first template that exists, falling back to bundled template |
||||
return select_template([ |
||||
instance.form_template, |
||||
settings.DJANGOCMS_FORMS_DEFAULT_TEMPLATE, |
||||
'djangocms_forms/form_template/default.html' |
||||
]) |
||||
|
||||
def render(self, context, instance, placeholder): |
||||
context = super(FormPlugin, self).render(context, instance, placeholder) |
||||
request = context['request'] |
||||
|
||||
form = FormBuilder( |
||||
initial={'referrer': request.path_info}, form_definition=instance, |
||||
label_suffix='', auto_id='%s') |
||||
|
||||
context.update({ |
||||
'form': form |
||||
}) |
||||
return context |
||||
|
||||
|
||||
plugin_pool.register_plugin(FormPlugin) |
||||
@ -0,0 +1,15 @@ |
||||
import json |
||||
|
||||
from django.core.serializers.json import DjangoJSONEncoder |
||||
from django.http import HttpResponse |
||||
|
||||
|
||||
class JsonResponse(HttpResponse): |
||||
|
||||
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True, **kwargs): |
||||
if safe and not isinstance(data, dict): |
||||
raise TypeError('In order to allow non-dict objects to be ' |
||||
'serialized set the safe parameter to False') |
||||
kwargs.setdefault('content_type', 'application/json') |
||||
data = json.dumps(data, cls=encoder) |
||||
super(JsonResponse, self).__init__(content=data, **kwargs) |
||||
@ -0,0 +1,66 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
from appconf import AppConf |
||||
from django.conf import settings # noqa |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
|
||||
|
||||
class DjangoCMSFormsConf(AppConf): |
||||
PLUGIN_MODULE = _('Generic') |
||||
PLUGIN_NAME = _('Form') |
||||
FIELDSETS = None |
||||
FILE_STORAGE_DIR = 'djangocms_forms' |
||||
FILE_STORAGE = settings.DEFAULT_FILE_STORAGE |
||||
|
||||
ALLOWED_FILE_TYPES = ( |
||||
'aac', 'ace', 'ai', 'aiff', 'avi', 'bmp', 'dir', 'doc', 'docx', 'dmg', |
||||
'eps', 'fla', 'flv', 'gif', 'gz', 'hqx', 'ico', 'indd', 'inx', 'jpg', |
||||
'jar', 'jpeg', 'md', 'mov', 'mp3', 'mp4', 'mpc', 'mkv', 'mpg', 'mpeg', |
||||
'ogg', 'odg', 'odf', 'odp', 'ods', 'odt', 'otf', 'pdf', 'png', 'pps', |
||||
'ppsx', 'ps', 'psd', 'rar', 'rm', 'rtf', 'sit', 'swf', 'tar', 'tga', |
||||
'tif', 'tiff', 'ttf', 'txt', 'wav', 'wma', 'wmv', 'xls', 'xlsx', 'xml', |
||||
'zip' |
||||
) |
||||
MAX_UPLOAD_SIZE = 5242880 # 5MB |
||||
|
||||
FIELD_TYPES = ( |
||||
('text', _('Text')), |
||||
('textarea', _('Text Area')), |
||||
('email', _('Email')), |
||||
('number', _('Number')), |
||||
('phone', _('Phone')), |
||||
('url', _('URL')), |
||||
('checkbox', _('Checkbox')), |
||||
('checkbox_multiple', _('Multi Checkbox')), |
||||
('select', _('Drop down')), |
||||
('radio', _('Radio')), |
||||
('file', _('File Upload')), |
||||
('date', _('Date')), |
||||
('time', _('Time')), |
||||
('password', _('Password')), |
||||
('hidden', _('Hidden')), |
||||
) |
||||
|
||||
DEFAULT_FIELD_TYPE = 'text' |
||||
|
||||
SPAM_PROTECTIONS = ( |
||||
(0, _('None')), |
||||
(1, _('Honeypot')), |
||||
(2, _('ReCAPTCHA')), |
||||
) |
||||
|
||||
DEFAULT_SPAM_PROTECTION = 0 |
||||
|
||||
TEMPLATES = ( |
||||
('djangocms_forms/form_template/default.html', _('Default')), |
||||
) |
||||
|
||||
DEFAULT_TEMPLATE = 'djangocms_forms/form_template/default.html' |
||||
|
||||
DATETIME_FORMAT = '%d/%m/%Y %H:%M' |
||||
EXPORT_FILENAME = 'export-{form_name}-%Y-%m-%d' |
||||
|
||||
HASHIDS_SALT = settings.SECRET_KEY |
||||
|
||||
class Meta: |
||||
prefix = 'djangocms_forms' |
||||
@ -0,0 +1,86 @@ |
||||
import os |
||||
|
||||
from django import forms |
||||
from django.core.exceptions import ValidationError |
||||
from django.db import models |
||||
from django.db.models import SET_NULL |
||||
from django.template.defaultfilters import filesizeformat |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
|
||||
from .conf import settings |
||||
|
||||
|
||||
class FormBuilderFileField(forms.FileField): |
||||
def __init__(self, *args, **kwargs): |
||||
self.max_upload_size = kwargs.pop( |
||||
'max_upload_size', settings.DJANGOCMS_FORMS_MAX_UPLOAD_SIZE) |
||||
allowed_file_types = kwargs.pop( |
||||
'allowed_file_types', settings.DJANGOCMS_FORMS_ALLOWED_FILE_TYPES) |
||||
self.allowed_file_types = [i.lstrip('.').lower() |
||||
for i in allowed_file_types] |
||||
|
||||
super(FormBuilderFileField, self).__init__(*args, **kwargs) |
||||
|
||||
def clean(self, *args, **kwargs): |
||||
uploaded_file = super(FormBuilderFileField, self).clean(*args, **kwargs) |
||||
if not uploaded_file: |
||||
if self.required: |
||||
raise forms.ValidationError(_('This field is required.')) |
||||
return uploaded_file |
||||
|
||||
if not os.path.splitext(uploaded_file.name)[1].lstrip('.').lower() in \ |
||||
self.allowed_file_types: |
||||
raise forms.ValidationError( |
||||
_('Sorry, this filetype is not allowed. ' |
||||
'Allowed filetype: %s') % ', '.join(self.allowed_file_types)) |
||||
|
||||
if uploaded_file._size > self.max_upload_size: |
||||
params = { |
||||
'max_size': filesizeformat(self.max_upload_size), |
||||
'size': filesizeformat(uploaded_file._size) |
||||
} |
||||
msg = _( |
||||
'Please keep file size under %(max_size)s. Current size is %(size)s.') % params |
||||
raise forms.ValidationError(msg) |
||||
|
||||
return uploaded_file |
||||
|
||||
|
||||
class PluginReferenceField(models.ForeignKey): |
||||
def __init__(self, *args, **kwargs): |
||||
kwargs.update({'null': True}) # always allow Null |
||||
kwargs.update({'editable': False}) # never allow edits in admin |
||||
kwargs.update({'on_delete': SET_NULL}) # never delete plugin |
||||
super(PluginReferenceField, self).__init__(*args, **kwargs) |
||||
|
||||
def _create(self, model_instance): |
||||
return self.rel.to._default_manager.create(name=model_instance.name) |
||||
|
||||
def pre_save(self, model_instance, add): |
||||
if not model_instance.pk and add: |
||||
setattr(model_instance, self.name, self._create(model_instance)) |
||||
else: |
||||
reference = getattr(model_instance, self.name) |
||||
if not reference: |
||||
setattr(model_instance, self.name, self._create(model_instance)) |
||||
reference = getattr(model_instance, self.name) |
||||
if reference.name != model_instance.name: |
||||
reference.name = model_instance.name |
||||
reference.save() |
||||
return super(PluginReferenceField, self).pre_save(model_instance, add) |
||||
|
||||
def south_field_triple(self): |
||||
"""Returns a suitable description of this field for South.""" |
||||
# We'll just introspect ourselves, since we inherit. |
||||
from south.modelsinspector import introspector |
||||
field_class = 'django.db.models.fields.related.ForeignKey' |
||||
args, kwargs = introspector(self) |
||||
return (field_class, args, kwargs) |
||||
|
||||
|
||||
class MultipleChoiceAutoCompleteField(forms.MultipleChoiceField): |
||||
|
||||
def validate(self, value): |
||||
if self.required and not value: |
||||
raise ValidationError(self.error_messages['required'], code='required') |
||||
return value |
||||
@ -0,0 +1,331 @@ |
||||
#!/usr/bin/env python |
||||
# -*- coding: utf-8 -*- |
||||
import re |
||||
|
||||
from django import forms |
||||
from django.contrib.admin.widgets import (AdminDateWidget, |
||||
FilteredSelectMultiple) |
||||
from django.core.mail import EmailMultiAlternatives |
||||
from django.core.urlresolvers import reverse |
||||
from django.template.defaultfilters import slugify |
||||
from django.template.loader import render_to_string |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
from ipware.ip import get_ip |
||||
from unidecode import unidecode |
||||
|
||||
from .fields import FormBuilderFileField, MultipleChoiceAutoCompleteField |
||||
from .models import Form, FormDefinition, FormField, FormSubmission |
||||
from .utils import int_to_hashid |
||||
from .widgets import DateInput, TelephoneInput, TimeInput |
||||
|
||||
|
||||
class FormFieldInlineForm(forms.ModelForm): |
||||
def clean(self): |
||||
cleaned_data = super(FormFieldInlineForm, self).clean() |
||||
|
||||
requires_choice_values = ['checkbox_multiple', 'select', 'radio'] |
||||
if (cleaned_data.get('field_type') in requires_choice_values and |
||||
not cleaned_data.get('choice_values')): |
||||
|
||||
error_msg = _('This field is required.') |
||||
self._errors['choice_values'] = self.error_class([error_msg]) |
||||
|
||||
return cleaned_data |
||||
|
||||
class Meta: |
||||
model = FormField |
||||
fields = '__all__' |
||||
|
||||
|
||||
class FormDefinitionAdminForm(forms.ModelForm): |
||||
def clean(self): |
||||
cleaned_data = super(FormDefinitionAdminForm, self).clean() |
||||
|
||||
populated_count = 0 |
||||
storage_fields = ('email_to', 'save_data', ) |
||||
|
||||
for field in storage_fields: |
||||
if cleaned_data.get(field, None): |
||||
populated_count += 1 |
||||
|
||||
if not populated_count: |
||||
error_msg = _( |
||||
'You must choose a storage option for this Form. ' |
||||
'You can choose to use multiple storage options if you prefer. ') |
||||
for field in storage_fields: |
||||
self._errors[field] = self.error_class([error_msg]) |
||||
|
||||
return cleaned_data |
||||
|
||||
class Meta: |
||||
model = FormDefinition |
||||
fields = '__all__' |
||||
|
||||
|
||||
class FormBuilder(forms.Form): |
||||
error_css_class = 'error' |
||||
required_css_class = 'required' |
||||
|
||||
form_id = forms.CharField(widget=forms.HiddenInput) |
||||
referrer = forms.CharField(widget=forms.HiddenInput) |
||||
|
||||
def __init__(self, form_definition, *args, **kwargs): |
||||
super(FormBuilder, self).__init__(*args, **kwargs) |
||||
self.form_definition = form_definition |
||||
self.field_names = [] |
||||
self.file_fields = [] |
||||
self.field_types = {} |
||||
|
||||
self.submission_url = reverse('djangocms_forms_submissions') |
||||
self.fields['form_id'].initial = int_to_hashid(form_definition.pk) |
||||
self.redirect_url = form_definition.redirect_url |
||||
|
||||
for field in form_definition.fields.all(): |
||||
if hasattr(self, 'prepare_%s' % field.field_type): |
||||
field_name = self.get_unique_field_name(field) |
||||
form_field = getattr(self, 'prepare_%s' % field.field_type)(field) |
||||
|
||||
self.fields[field_name] = form_field |
||||
|
||||
if isinstance(form_field, FormBuilderFileField): |
||||
self.file_fields.append(field_name) |
||||
|
||||
def get_unique_field_name(self, field): |
||||
field_name = '%s' % (slugify(unidecode(field.label)).replace('-', '_')) |
||||
if field_name in self.field_names: |
||||
i = 1 |
||||
while True: |
||||
if i > 1: |
||||
if i > 2: |
||||
field_name = field_name.rsplit('_', 1)[0] |
||||
field_name = '%s_%s' % (field_name, i) |
||||
if field_name not in self.field_names: |
||||
break |
||||
i += 1 |
||||
|
||||
self.field_names.append(field_name) |
||||
self.field_types[field_name] = field.field_type |
||||
return field_name |
||||
|
||||
def split_choices(self, choices): |
||||
return [x.strip() for x in choices.split(',') if x.strip()] |
||||
|
||||
def to_bool(self, value): |
||||
return value.lower() in ('yes', 'y', 'true', 't', '1') |
||||
|
||||
def prepare_text(self, field): |
||||
kwargs = field.field_attrs() |
||||
if field.placeholder_text and not field.initial: |
||||
kwargs.update({ |
||||
'widget': forms.TextInput({ |
||||
'placeholder': field.placeholder_text |
||||
}) |
||||
}) |
||||
return forms.CharField(**kwargs) |
||||
|
||||
def prepare_textarea(self, field): |
||||
kwargs = field.field_attrs() |
||||
kwargs.update({ |
||||
'widget': forms.Textarea({ |
||||
'placeholder': field.placeholder_text or '', |
||||
}) |
||||
}) |
||||
return forms.CharField(**kwargs) |
||||
|
||||
def prepare_email(self, field): |
||||
kwargs = field.field_attrs() |
||||
kwargs.update({ |
||||
'widget': forms.EmailInput({ |
||||
'placeholder': field.placeholder_text or '', |
||||
'autocomplete': 'email', |
||||
}), |
||||
}) |
||||
return forms.EmailField(**kwargs) |
||||
|
||||
def prepare_checkbox(self, field): |
||||
kwargs = field.field_attrs() |
||||
if field.initial: |
||||
kwargs.update({ |
||||
'initial': self.to_bool(field.initial) |
||||
}) |
||||
return forms.BooleanField(**kwargs) |
||||
|
||||
def prepare_checkbox_multiple(self, field): |
||||
kwargs = field.field_attrs() |
||||
kwargs.update({ |
||||
'widget': forms.CheckboxSelectMultiple(), |
||||
'choices': field.get_choices(), |
||||
}) |
||||
|
||||
if field.initial: |
||||
kwargs.update({ |
||||
'initial': self.split_choices(field.initial) |
||||
}) |
||||
return forms.MultipleChoiceField(**kwargs) |
||||
|
||||
def prepare_select(self, field): |
||||
kwargs = field.field_attrs() |
||||
if field.choice_values: |
||||
choice_list = field.get_choices() |
||||
if not field.required: |
||||
choice_list.insert(0, ('', field.placeholder_text or _('Please select an option'))) |
||||
kwargs.update({ |
||||
'choices': choice_list |
||||
}) |
||||
return forms.ChoiceField(**kwargs) |
||||
|
||||
def prepare_radio(self, field): |
||||
kwargs = field.field_attrs() |
||||
kwargs.update({ |
||||
'widget': forms.RadioSelect(), |
||||
'choices': field.get_choices(), |
||||
}) |
||||
return forms.ChoiceField(**kwargs) |
||||
|
||||
def prepare_file(self, field): |
||||
kwargs = field.field_attrs() |
||||
if field.choice_values: |
||||
regex = re.compile('[\s]*\n[\s]*') |
||||
choices = regex.split(field.choice_values) |
||||
kwargs.update({ |
||||
'allowed_file_types': [i.lstrip('.').lower() for i in choices] |
||||
}) |
||||
return FormBuilderFileField(**kwargs) |
||||
|
||||
def prepare_date(self, field): |
||||
kwargs = field.field_attrs() |
||||
kwargs.update({ |
||||
'widget': DateInput(), |
||||
}) |
||||
return forms.DateField(**kwargs) |
||||
|
||||
def prepare_time(self, field): |
||||
# @todo: needs proper widget |
||||
kwargs = field.field_attrs() |
||||
kwargs.update({ |
||||
'widget': TimeInput(), |
||||
}) |
||||
return forms.TimeField(**kwargs) |
||||
|
||||
def prepare_hidden(self, field): |
||||
kwargs = field.field_attrs() |
||||
kwargs.update({ |
||||
'widget': forms.HiddenInput(), |
||||
}) |
||||
return forms.CharField(**kwargs) |
||||
|
||||
def prepare_number(self, field): |
||||
kwargs = field.field_attrs() |
||||
return forms.IntegerField(**kwargs) |
||||
|
||||
def prepare_url(self, field): |
||||
kwargs = field.field_attrs() |
||||
return forms.URLField(**kwargs) |
||||
|
||||
def prepare_password(self, field): |
||||
kwargs = field.field_attrs() |
||||
kwargs.update({ |
||||
'widget': forms.PasswordInput(), |
||||
}) |
||||
return forms.CharField(**kwargs) |
||||
|
||||
def prepare_phone(self, field): |
||||
kwargs = field.field_attrs() |
||||
kwargs.update({ |
||||
'widget': TelephoneInput(), |
||||
}) |
||||
return forms.CharField(**kwargs) |
||||
|
||||
def save(self, request): |
||||
form_data = [] |
||||
for field in self.field_names: |
||||
value = self.cleaned_data[field] |
||||
if hasattr(value, 'url'): |
||||
value = value.url |
||||
form_data.append({ |
||||
'name': field, |
||||
'label': self.fields[field].label, |
||||
'value': value, |
||||
'type': self.field_types[field], |
||||
}) |
||||
|
||||
referrer = self.cleaned_data.get('referrer', '') |
||||
|
||||
if self.form_definition.save_data: |
||||
self.save_to_db(form_data, request=request, referrer=referrer) |
||||
if self.form_definition.email_to: |
||||
self.email_submission(form_data, request=request, referrer=referrer) |
||||
|
||||
def save_to_db(self, form_data, request, referrer): |
||||
user = request.user if request.user.is_authenticated() else None |
||||
# Если есть сессия для этой формы, то сохраняем в неё. |
||||
form_slug = "form-%d" % self.form_definition.plugin_reference.id |
||||
if request.session.get(form_slug, False): |
||||
try: |
||||
form_instance = FormSubmission.objects.get(id=request.session.get(form_slug, False)) |
||||
form_instance.form_data += form_data |
||||
form_instance.save() |
||||
# Удаляем сессию, если это последний шаг. Признак последнего шага |
||||
for data in form_data: |
||||
if data['name'] == 'end_session': |
||||
request.session[form_slug] = None |
||||
return |
||||
except (FormSubmission.DoesNotExist): |
||||
pass |
||||
form_instance = FormSubmission.objects.create( |
||||
plugin=self.form_definition.plugin_reference, |
||||
ip=get_ip(request), |
||||
referrer=referrer, |
||||
form_data=form_data, |
||||
created_by=user) |
||||
|
||||
request.session[form_slug] = form_instance.id |
||||
|
||||
def email_submission(self, form_data, request, referrer): |
||||
mail_to = re.compile('\s*[,;]+\s*').split(self.form_definition.email_to) |
||||
mail_from = self.form_definition.email_from or None |
||||
mail_subject = self.form_definition.email_subject or \ |
||||
'Form Submission - %s' % self.form_definition.name |
||||
context = { |
||||
'form': self.form_definition, |
||||
'referrer': referrer, |
||||
'title': mail_subject, |
||||
'form_data': form_data, |
||||
'request': request, |
||||
'recipients': mail_to, |
||||
} |
||||
|
||||
message = render_to_string('djangocms_forms/email_template/email.txt', context) |
||||
message_html = render_to_string('djangocms_forms/email_template/email.html', context) |
||||
|
||||
email = EmailMultiAlternatives(mail_subject, message, mail_from, mail_to) |
||||
email.attach_alternative(message_html, 'text/html') |
||||
|
||||
if self.files and self.form_definition.email_uploaded_files: |
||||
for file_path in self.files: |
||||
email.attach_file(file_path) |
||||
|
||||
email.send(fail_silently=False) |
||||
|
||||
|
||||
class SubmissionExportForm(forms.Form): |
||||
FORMAT_CHOICES = ( |
||||
('csv', _('CSV')), |
||||
('json', _('JSON')), |
||||
('yaml', _('YAML')), |
||||
('xls', _('Microsoft Excel')), |
||||
) |
||||
|
||||
form = forms.ModelChoiceField( |
||||
queryset=Form.active_objects.all(), label=_('Select a Form'), |
||||
error_messages={'required': _('Please select a form.')}, |
||||
help_text=_('Select the form you would like to export entry data from. ' |
||||
'You may only export data from one form at a time.')) |
||||
file_type = forms.ChoiceField(choices=FORMAT_CHOICES, initial='xls', required=False) |
||||
headers = MultipleChoiceAutoCompleteField( |
||||
label=_('Fields'), required=False, |
||||
widget=FilteredSelectMultiple(verbose_name=_('Fields'), is_stacked=False),) |
||||
from_date = forms.DateField( |
||||
label=_('From date'), required=False, widget=AdminDateWidget) |
||||
to_date = forms.DateField( |
||||
label=_('To date'), required=False, widget=AdminDateWidget) |
||||
@ -0,0 +1,9 @@ |
||||
from django.db import models |
||||
from django.db.models import Count |
||||
|
||||
|
||||
class ActiveFormManager(models.Manager): |
||||
def get_queryset(self): |
||||
qs = super(ActiveFormManager, self).get_queryset() |
||||
return qs.annotate(submission_count=Count('submissions')) \ |
||||
.filter(submission_count__gt=0) |
||||
@ -0,0 +1,97 @@ |
||||
# -*- coding: utf-8 -*- |
||||
from __future__ import unicode_literals |
||||
|
||||
from django.db import models, migrations |
||||
import jsonfield.fields |
||||
import djangocms_forms.fields |
||||
import django.db.models.deletion |
||||
import cms.models.fields |
||||
from django.conf import settings |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('cms', '__first__'), |
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.CreateModel( |
||||
name='Form', |
||||
fields=[ |
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
||||
('name', models.CharField(verbose_name='Name', max_length=255, editable=False, db_index=True)), |
||||
], |
||||
options={ |
||||
'verbose_name': 'Form', |
||||
'verbose_name_plural': 'Forms', |
||||
}, |
||||
bases=(models.Model,), |
||||
), |
||||
migrations.CreateModel( |
||||
name='FormDefinition', |
||||
fields=[ |
||||
('cmsplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin')), |
||||
('name', models.CharField(max_length=255, verbose_name='Form Name')), |
||||
('title', models.CharField(max_length=150, verbose_name='Title', blank=True)), |
||||
('description', models.TextField(verbose_name='Description', blank=True)), |
||||
('submit_btn_txt', models.CharField(default='Submit', help_text="Text for the Submit Button. The default is 'Submit'", max_length=100, verbose_name='Submit Button Text')), |
||||
('post_submit_msg', models.TextField(default='Thank You', help_text='Display this message to users after they submit your form.', verbose_name='Post Submit Message', blank=True)), |
||||
('success_redirect', models.BooleanField(default=False, help_text='HTTP redirect after successful submission', verbose_name='Redirect?')), |
||||
('external_redirect', models.URLField(help_text='e.g. http://example.com/thank-you', verbose_name='External URL', blank=True)), |
||||
('email_to', models.CharField(help_text='Separate several addresses with a comma.', max_length=255, verbose_name='Send form data to e-mail address', blank=True)), |
||||
('email_from', models.EmailField(max_length=255, verbose_name='Sender Email Address', blank=True)), |
||||
('email_subject', models.CharField(max_length=255, verbose_name='Email Subject', blank=True)), |
||||
('email_uploaded_files', models.BooleanField(default=True, verbose_name='Send uploaded files as email attachments')), |
||||
('save_data', models.BooleanField(default=True, help_text='Logs all form submissions to the database.', verbose_name='Save to database')), |
||||
('spam_protection', models.SmallIntegerField(default=0, verbose_name='Spam Protection', choices=[(0, 'None'), (1, 'Honeypot'), (2, 'ReCAPTCHA')])), |
||||
('form_template', models.CharField(default=b'djangocms_forms/form_template/default.html', max_length=150, verbose_name='Form Template', blank=True, choices=[(b'djangocms_forms/form_template/default.html', 'Default')])), |
||||
('page_redirect', cms.models.fields.PageField(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='cms.Page', help_text='A page has priority over an external URL', null=True, verbose_name='Page URL')), |
||||
('plugin_reference', djangocms_forms.fields.PluginReferenceField(related_name='plugin', on_delete=django.db.models.deletion.SET_NULL, editable=False, to='djangocms_forms.Form', null=True)), |
||||
], |
||||
options={ |
||||
'verbose_name': 'Form', |
||||
'verbose_name_plural': 'Forms', |
||||
}, |
||||
bases=('cms.cmsplugin',), |
||||
), |
||||
migrations.CreateModel( |
||||
name='FormField', |
||||
fields=[ |
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
||||
('field_type', models.CharField(default=b'text', max_length=100, verbose_name='Field Type', choices=[(b'text', 'Text'), (b'textarea', 'Text Area'), (b'email', 'Email'), (b'number', 'Number'), (b'phone', 'Phone'), (b'url', 'URL'), (b'checkbox', 'Checkbox'), (b'checkbox_multiple', 'Multi Checkbox'), (b'select', 'Drop down'), (b'radio', 'Radio'), (b'file', 'File Upload'), (b'date', 'Date'), (b'time', 'Time'), (b'password', 'Password'), (b'hidden', 'Hidden')])), |
||||
('label', models.CharField(max_length=255, verbose_name='name')), |
||||
('placeholder_text', models.CharField(max_length=100, verbose_name='Placeholder Text', blank=True)), |
||||
('required', models.BooleanField(default=True, verbose_name='Required')), |
||||
('help_text', models.TextField(help_text='A description / instructions for this field.', verbose_name='Description', blank=True)), |
||||
('initial', models.CharField(max_length=255, verbose_name='Default Value', blank=True)), |
||||
('choice_values', models.TextField(help_text='Enter options one per line. For "File Upload" field type, enter allowed filetype (e.g .pdf) one per line.', verbose_name='Choices', blank=True)), |
||||
('position', models.PositiveIntegerField(null=True, verbose_name='Position', blank=True)), |
||||
('form', models.ForeignKey(related_name='fields', to='djangocms_forms.FormDefinition')), |
||||
], |
||||
options={ |
||||
'ordering': ('position',), |
||||
'verbose_name': 'field', |
||||
'verbose_name_plural': 'fields', |
||||
}, |
||||
bases=(models.Model,), |
||||
), |
||||
migrations.CreateModel( |
||||
name='FormSubmission', |
||||
fields=[ |
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), |
||||
('creation_date', models.DateTimeField(auto_now=True, verbose_name='Date')), |
||||
('ip', models.GenericIPAddressField(null=True, verbose_name=b'IP', blank=True)), |
||||
('form_data', jsonfield.fields.JSONField(verbose_name='Form Data')), |
||||
('created_by', models.ForeignKey(editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='User')), |
||||
('plugin', models.ForeignKey(related_name='submissions', editable=False, to='djangocms_forms.Form', verbose_name='Form')), |
||||
], |
||||
options={ |
||||
'ordering': ('-creation_date',), |
||||
'verbose_name': 'Form Submission', |
||||
'verbose_name_plural': 'Form Submissions', |
||||
}, |
||||
bases=(models.Model,), |
||||
), |
||||
] |
||||
@ -0,0 +1,26 @@ |
||||
# -*- coding: utf-8 -*- |
||||
from __future__ import unicode_literals |
||||
|
||||
from django.db import models, migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('djangocms_forms', '0001_initial'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AlterModelOptions( |
||||
name='form', |
||||
options={'verbose_name': 'form', 'verbose_name_plural': 'forms'}, |
||||
), |
||||
migrations.AlterModelOptions( |
||||
name='formdefinition', |
||||
options={'verbose_name': 'form', 'verbose_name_plural': 'forms'}, |
||||
), |
||||
migrations.AlterModelOptions( |
||||
name='formsubmission', |
||||
options={'ordering': ('-creation_date',), 'verbose_name': 'form submission', 'verbose_name_plural': 'form submissions', 'permissions': (('export_formsubmission', 'Can export Form Submission'),)}, |
||||
), |
||||
] |
||||
@ -0,0 +1,19 @@ |
||||
# -*- coding: utf-8 -*- |
||||
from __future__ import unicode_literals |
||||
|
||||
from django.db import models, migrations |
||||
|
||||
|
||||
class Migration(migrations.Migration): |
||||
|
||||
dependencies = [ |
||||
('djangocms_forms', '0002_alter_model_options'), |
||||
] |
||||
|
||||
operations = [ |
||||
migrations.AddField( |
||||
model_name='formsubmission', |
||||
name='referrer', |
||||
field=models.CharField(max_length=150, verbose_name='Referrer URL', blank=True), |
||||
), |
||||
] |
||||
@ -0,0 +1,176 @@ |
||||
import re |
||||
|
||||
from cms.models import CMSPlugin |
||||
from cms.models.fields import PageField |
||||
from django.db import models |
||||
from django.template.defaultfilters import slugify |
||||
from django.utils.encoding import python_2_unicode_compatible |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
from jsonfield import JSONField |
||||
from unidecode import unidecode |
||||
|
||||
from .conf import settings |
||||
from .fields import PluginReferenceField |
||||
from .managers import ActiveFormManager |
||||
|
||||
|
||||
@python_2_unicode_compatible |
||||
class Form(models.Model): |
||||
name = models.CharField(_('Name'), max_length=255, db_index=True, editable=False) |
||||
|
||||
objects = models.Manager() |
||||
active_objects = ActiveFormManager() |
||||
|
||||
class Meta: |
||||
verbose_name = _('form') |
||||
verbose_name_plural = _('forms') |
||||
|
||||
def __str__(self): |
||||
return self.name |
||||
|
||||
|
||||
@python_2_unicode_compatible |
||||
class FormDefinition(CMSPlugin): |
||||
name = models.CharField(_('Form Name'), max_length=255) |
||||
|
||||
title = models.CharField(_('Title'), max_length=150, blank=True) |
||||
description = models.TextField(_('Description'), blank=True) |
||||
submit_btn_txt = models.CharField( |
||||
_('Submit Button Text'), max_length=100, default=_('Submit'), |
||||
help_text=_('Text for the Submit Button. The default is \'Submit\'')) |
||||
|
||||
post_submit_msg = models.TextField( |
||||
_('Post Submit Message'), blank=True, default=_('Thank You'), |
||||
help_text=_('Display this message to users after they submit your form.')) |
||||
|
||||
# 'HTTP redirect after successful submission' |
||||
success_redirect = models.BooleanField( |
||||
_('Redirect?'), default=False, |
||||
help_text=_('HTTP redirect after successful submission')) |
||||
page_redirect = PageField( |
||||
verbose_name=_('Page URL'), blank=True, null=True, |
||||
on_delete=models.SET_NULL, |
||||
help_text=_('A page has priority over an external URL')) |
||||
external_redirect = models.URLField( |
||||
_('External URL'), blank=True, |
||||
help_text=_('e.g. http://example.com/thank-you')) |
||||
|
||||
# Email |
||||
email_to = models.CharField( |
||||
_('Send form data to e-mail address'), max_length=255, blank=True, |
||||
help_text=_('Separate several addresses with a comma.')) |
||||
email_from = models.EmailField(_('Sender Email Address'), max_length=255, blank=True) |
||||
email_subject = models.CharField(_('Email Subject'), max_length=255, blank=True) |
||||
email_uploaded_files = models.BooleanField( |
||||
_('Send uploaded files as email attachments'), default=True) |
||||
|
||||
# Save to database |
||||
save_data = models.BooleanField( |
||||
_('Save to database'), default=True, |
||||
help_text=_('Logs all form submissions to the database.')) |
||||
spam_protection = models.SmallIntegerField( |
||||
_('Spam Protection'), |
||||
choices=settings.DJANGOCMS_FORMS_SPAM_PROTECTIONS, |
||||
default=settings.DJANGOCMS_FORMS_DEFAULT_SPAM_PROTECTION) |
||||
|
||||
form_template = models.CharField( |
||||
_('Form Template'), max_length=150, blank=True, |
||||
choices=settings.DJANGOCMS_FORMS_TEMPLATES, |
||||
default=settings.DJANGOCMS_FORMS_DEFAULT_TEMPLATE, |
||||
) |
||||
|
||||
plugin_reference = PluginReferenceField(Form, related_name='plugin') |
||||
|
||||
class Meta: |
||||
verbose_name_plural = _('forms') |
||||
verbose_name = _('form') |
||||
|
||||
def __str__(self): |
||||
return self.name |
||||
|
||||
@property |
||||
def redirect_url(self): |
||||
if self.page_redirect: |
||||
return self.page_redirect.get_absolute_url() |
||||
elif self.external_redirect: |
||||
return self.external_redirect |
||||
|
||||
@property |
||||
def upload_to(self): |
||||
return '%s-%s' % ( |
||||
slugify(unidecode(self.name)).replace('_', '-'), |
||||
self.plugin_reference_id) |
||||
|
||||
def copy_relations(self, oldinstance): |
||||
for field in oldinstance.fields.all(): |
||||
field.pk = None |
||||
field.form = self |
||||
field.save() |
||||
|
||||
|
||||
@python_2_unicode_compatible |
||||
class FormField(models.Model): |
||||
form = models.ForeignKey(FormDefinition, related_name='fields') |
||||
field_type = models.CharField( |
||||
_('Field Type'), max_length=100, |
||||
choices=settings.DJANGOCMS_FORMS_FIELD_TYPES, |
||||
default=settings.DJANGOCMS_FORMS_DEFAULT_FIELD_TYPE) |
||||
label = models.CharField(_('name'), max_length=255) |
||||
placeholder_text = models.CharField(_('Placeholder Text'), blank=True, max_length=100) |
||||
required = models.BooleanField(_('Required'), default=True) |
||||
help_text = models.TextField( |
||||
_('Description'), blank=True, |
||||
help_text=_('A description / instructions for this field.')) |
||||
initial = models.CharField(_('Default Value'), max_length=255, blank=True) |
||||
choice_values = models.TextField( |
||||
_('Choices'), blank=True, |
||||
help_text=_('Enter options one per line. For "File Upload" ' |
||||
'field type, enter allowed filetype (e.g .pdf) one per line.')) |
||||
position = models.PositiveIntegerField(_('Position'), blank=True, null=True) |
||||
|
||||
class Meta: |
||||
verbose_name_plural = _('fields') |
||||
verbose_name = _('field') |
||||
ordering = ('position', ) |
||||
|
||||
def __str__(self): |
||||
return self.label |
||||
|
||||
def field_attrs(self): |
||||
args = { |
||||
'required': self.required, |
||||
'label': self.label if self.label else '', |
||||
'initial': self.initial if self.initial else None, |
||||
'help_text': self.help_text, |
||||
} |
||||
return args |
||||
|
||||
def get_choices(self): |
||||
if self.choice_values: |
||||
regex = re.compile('[\s]*\n[\s]*') |
||||
choices = regex.split(self.choice_values) |
||||
return [(str(choice.encode('utf-8')), str(choice.encode('utf-8'))) for choice in choices] |
||||
|
||||
|
||||
@python_2_unicode_compatible |
||||
class FormSubmission(models.Model): |
||||
plugin = models.ForeignKey( |
||||
Form, verbose_name=_('Form'), editable=False, related_name='submissions') |
||||
creation_date = models.DateTimeField(_('Date'), auto_now=True) |
||||
created_by = models.ForeignKey( |
||||
settings.AUTH_USER_MODEL, verbose_name=_('User'), editable=False, null=True) |
||||
ip = models.GenericIPAddressField(verbose_name='IP', blank=True, null=True) |
||||
referrer = models.CharField(_('Referrer URL'), max_length=150, blank=True) |
||||
|
||||
form_data = JSONField(_('Form Data')) |
||||
|
||||
class Meta: |
||||
verbose_name_plural = _('form submissions') |
||||
verbose_name = _('form submission') |
||||
ordering = ('-creation_date', ) |
||||
permissions = ( |
||||
('export_formsubmission', 'Can export Form Submission'), |
||||
) |
||||
|
||||
def __str__(self): |
||||
return u'%s' % self.plugin |
||||
@ -0,0 +1,35 @@ |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
|
||||
TEXT = 'text' |
||||
TEXTAREA = 'textarea' |
||||
EMAIL = 'email' |
||||
CHECKBOX = 'checkbox' |
||||
CHECKBOX_MULTIPLE = 'checkbox_multiple' |
||||
SELECT = 'select' |
||||
RADIO = 'radio' |
||||
FILE = 'file' |
||||
DATE = 'date' |
||||
TIME = 'time' |
||||
HIDDEN = 'hidden' |
||||
NUMBER = 'number' |
||||
URL = 'url' |
||||
PASSWORD = 'password' |
||||
PHONE = 'phone' |
||||
|
||||
FIELD_TYPES = ( |
||||
(TEXT, _('Text')), |
||||
(TEXTAREA, _('Text Area')), |
||||
(EMAIL, _('Email')), |
||||
(NUMBER, _('Number')), |
||||
(PHONE, _('Phone')), |
||||
(URL, _('URL')), |
||||
(CHECKBOX, _('Checkbox')), |
||||
(CHECKBOX_MULTIPLE, _('Multi Checkbox')), |
||||
(SELECT, _('Drop down')), |
||||
(RADIO, _('Radio')), |
||||
(FILE, _('File Upload')), |
||||
(DATE, _('Date')), |
||||
(TIME, _('Time')), |
||||
(PASSWORD, _('Password')), |
||||
(HIDDEN, _('Hidden')), |
||||
) |
||||
@ -0,0 +1,5 @@ |
||||
from __future__ import unicode_literals |
||||
|
||||
from django.dispatch import Signal |
||||
|
||||
form_submission = Signal(providing_args=['form', 'cleaned_data']) |
||||
@ -0,0 +1,226 @@ |
||||
# -*- coding: utf-8 -*- |
||||
from south.utils import datetime_utils as 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 'Form' |
||||
db.create_table(u'djangocms_forms_form', ( |
||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
||||
('name', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), |
||||
)) |
||||
db.send_create_signal(u'djangocms_forms', ['Form']) |
||||
|
||||
# Adding model 'FormDefinition' |
||||
db.create_table(u'djangocms_forms_formdefinition', ( |
||||
(u'cmsplugin_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['cms.CMSPlugin'], unique=True, primary_key=True)), |
||||
('name', self.gf('django.db.models.fields.CharField')(max_length=255)), |
||||
('title', self.gf('django.db.models.fields.CharField')(max_length=150, blank=True)), |
||||
('description', self.gf('django.db.models.fields.TextField')(blank=True)), |
||||
('submit_btn_txt', self.gf('django.db.models.fields.CharField')(default=u'Submit', max_length=100)), |
||||
('post_submit_msg', self.gf('django.db.models.fields.TextField')(default=u'Thank You', blank=True)), |
||||
('success_redirect', self.gf('django.db.models.fields.BooleanField')(default=False)), |
||||
('page_redirect', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['cms.Page'], null=True, on_delete=models.SET_NULL, blank=True)), |
||||
('external_redirect', self.gf('django.db.models.fields.URLField')(max_length=200, blank=True)), |
||||
('email_to', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), |
||||
('email_from', self.gf('django.db.models.fields.EmailField')(max_length=255, blank=True)), |
||||
('email_subject', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), |
||||
('email_uploaded_files', self.gf('django.db.models.fields.BooleanField')(default=True)), |
||||
('save_data', self.gf('django.db.models.fields.BooleanField')(default=True)), |
||||
('spam_protection', self.gf('django.db.models.fields.SmallIntegerField')(default=0)), |
||||
('form_template', self.gf('django.db.models.fields.CharField')(default='djangocms_forms/form_template/default.html', max_length=150, blank=True)), |
||||
('plugin_reference', self.gf('django.db.models.fields.related.ForeignKey')(related_name='plugin', null=True, on_delete=models.SET_NULL, to=orm['djangocms_forms.Form'])), |
||||
)) |
||||
db.send_create_signal(u'djangocms_forms', ['FormDefinition']) |
||||
|
||||
# Adding model 'FormField' |
||||
db.create_table(u'djangocms_forms_formfield', ( |
||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
||||
('form', self.gf('django.db.models.fields.related.ForeignKey')(related_name='fields', to=orm['djangocms_forms.FormDefinition'])), |
||||
('field_type', self.gf('django.db.models.fields.CharField')(default='text', max_length=100)), |
||||
('label', self.gf('django.db.models.fields.CharField')(max_length=255)), |
||||
('placeholder_text', self.gf('django.db.models.fields.CharField')(max_length=100, blank=True)), |
||||
('required', self.gf('django.db.models.fields.BooleanField')(default=True)), |
||||
('help_text', self.gf('django.db.models.fields.TextField')(blank=True)), |
||||
('initial', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), |
||||
('choice_values', self.gf('django.db.models.fields.TextField')(blank=True)), |
||||
('position', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True)), |
||||
)) |
||||
db.send_create_signal(u'djangocms_forms', ['FormField']) |
||||
|
||||
# Adding model 'FormSubmission' |
||||
db.create_table(u'djangocms_forms_formsubmission', ( |
||||
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), |
||||
('plugin', self.gf('django.db.models.fields.related.ForeignKey')(related_name='submissions', to=orm['djangocms_forms.Form'])), |
||||
('creation_date', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), |
||||
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)), |
||||
('ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39, null=True, blank=True)), |
||||
('form_data', self.gf('jsonfield.fields.JSONField')()), |
||||
)) |
||||
db.send_create_signal(u'djangocms_forms', ['FormSubmission']) |
||||
|
||||
|
||||
def backwards(self, orm): |
||||
# Deleting model 'Form' |
||||
db.delete_table(u'djangocms_forms_form') |
||||
|
||||
# Deleting model 'FormDefinition' |
||||
db.delete_table(u'djangocms_forms_formdefinition') |
||||
|
||||
# Deleting model 'FormField' |
||||
db.delete_table(u'djangocms_forms_formfield') |
||||
|
||||
# Deleting model 'FormSubmission' |
||||
db.delete_table(u'djangocms_forms_formsubmission') |
||||
|
||||
|
||||
models = { |
||||
u'auth.group': { |
||||
'Meta': {'object_name': 'Group'}, |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
||||
}, |
||||
u'auth.permission': { |
||||
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, |
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
||||
}, |
||||
u'auth.user': { |
||||
'Meta': {'object_name': 'User'}, |
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), |
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
||||
}, |
||||
'cms.cmsplugin': { |
||||
'Meta': {'object_name': 'CMSPlugin'}, |
||||
'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', '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'}), |
||||
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), |
||||
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) |
||||
}, |
||||
'cms.page': { |
||||
'Meta': {'ordering': "('tree_id', 'lft')", 'unique_together': "(('publisher_is_draft', 'application_namespace'), ('reverse_id', 'site', 'publisher_is_draft'))", 'object_name': 'Page'}, |
||||
'application_namespace': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
||||
'application_urls': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '200', 'null': 'True', 'blank': 'True'}), |
||||
'changed_by': ('django.db.models.fields.CharField', [], {'max_length': '70'}), |
||||
'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
||||
'created_by': ('django.db.models.fields.CharField', [], {'max_length': '70'}), |
||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'in_navigation': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), |
||||
'is_home': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), |
||||
'languages': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
||||
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), |
||||
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), |
||||
'limit_visibility_in_menu': ('django.db.models.fields.SmallIntegerField', [], {'default': 'None', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), |
||||
'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
||||
'navigation_extenders': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), |
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['cms.Page']"}), |
||||
'placeholders': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cms.Placeholder']", 'symmetrical': 'False'}), |
||||
'publication_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), |
||||
'publication_end_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), |
||||
'publisher_is_draft': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), |
||||
'publisher_public': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'publisher_draft'", 'unique': 'True', 'null': 'True', 'to': "orm['cms.Page']"}), |
||||
'reverse_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '40', 'null': 'True', 'blank': 'True'}), |
||||
'revision_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), |
||||
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), |
||||
'site': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'djangocms_pages'", 'to': u"orm['sites.Site']"}), |
||||
'soft_root': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), |
||||
'template': ('django.db.models.fields.CharField', [], {'default': "'INHERIT'", 'max_length': '100'}), |
||||
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), |
||||
'xframe_options': ('django.db.models.fields.IntegerField', [], {'default': '0'}) |
||||
}, |
||||
'cms.placeholder': { |
||||
'Meta': {'object_name': 'Placeholder'}, |
||||
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) |
||||
}, |
||||
u'contenttypes.contenttype': { |
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
||||
}, |
||||
u'djangocms_forms.form': { |
||||
'Meta': {'object_name': 'Form'}, |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) |
||||
}, |
||||
u'djangocms_forms.formdefinition': { |
||||
'Meta': {'object_name': 'FormDefinition', '_ormbases': ['cms.CMSPlugin']}, |
||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}), |
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
||||
'email_from': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'blank': 'True'}), |
||||
'email_subject': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
||||
'email_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
||||
'email_uploaded_files': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
||||
'external_redirect': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), |
||||
'form_template': ('django.db.models.fields.CharField', [], {'default': "'djangocms_forms/form_template/default.html'", 'max_length': '150', 'blank': 'True'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
||||
'page_redirect': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Page']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), |
||||
'plugin_reference': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'plugin'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['djangocms_forms.Form']"}), |
||||
'post_submit_msg': ('django.db.models.fields.TextField', [], {'default': "u'Thank You'", 'blank': 'True'}), |
||||
'save_data': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
||||
'spam_protection': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), |
||||
'submit_btn_txt': ('django.db.models.fields.CharField', [], {'default': "u'Submit'", 'max_length': '100'}), |
||||
'success_redirect': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '150', 'blank': 'True'}) |
||||
}, |
||||
u'djangocms_forms.formfield': { |
||||
'Meta': {'ordering': "('position',)", 'object_name': 'FormField'}, |
||||
'choice_values': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
||||
'field_type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '100'}), |
||||
'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': u"orm['djangocms_forms.FormDefinition']"}), |
||||
'help_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'initial': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
||||
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
||||
'placeholder_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), |
||||
'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), |
||||
'required': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) |
||||
}, |
||||
u'djangocms_forms.formsubmission': { |
||||
'Meta': {'ordering': "('-creation_date',)", 'object_name': 'FormSubmission'}, |
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True'}), |
||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
||||
'form_data': ('jsonfield.fields.JSONField', [], {}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}), |
||||
'plugin': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'submissions'", 'to': u"orm['djangocms_forms.Form']"}) |
||||
}, |
||||
u'sites.site': { |
||||
'Meta': {'ordering': "(u'domain',)", 'object_name': 'Site', 'db_table': "u'django_site'"}, |
||||
'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
||||
} |
||||
} |
||||
|
||||
complete_apps = ['djangocms_forms'] |
||||
@ -0,0 +1,168 @@ |
||||
# -*- coding: utf-8 -*- |
||||
from south.utils import datetime_utils as datetime |
||||
from south.db import db |
||||
from south.v2 import SchemaMigration |
||||
from django.db import models |
||||
|
||||
|
||||
class Migration(SchemaMigration): |
||||
|
||||
def forwards(self, orm): |
||||
# Adding field 'FormSubmission.referrer' |
||||
db.add_column(u'djangocms_forms_formsubmission', 'referrer', |
||||
self.gf('django.db.models.fields.CharField')(default='', max_length=150, blank=True), |
||||
keep_default=False) |
||||
|
||||
|
||||
def backwards(self, orm): |
||||
# Deleting field 'FormSubmission.referrer' |
||||
db.delete_column(u'djangocms_forms_formsubmission', 'referrer') |
||||
|
||||
|
||||
models = { |
||||
u'auth.group': { |
||||
'Meta': {'object_name': 'Group'}, |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), |
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) |
||||
}, |
||||
u'auth.permission': { |
||||
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, |
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
||||
}, |
||||
u'auth.user': { |
||||
'Meta': {'object_name': 'User'}, |
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), |
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), |
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), |
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), |
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) |
||||
}, |
||||
'cms.cmsplugin': { |
||||
'Meta': {'object_name': 'CMSPlugin'}, |
||||
'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'language': ('django.db.models.fields.CharField', [], {'max_length': '15', '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'}), |
||||
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), |
||||
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}) |
||||
}, |
||||
'cms.page': { |
||||
'Meta': {'ordering': "('tree_id', 'lft')", 'unique_together': "(('publisher_is_draft', 'application_namespace'), ('reverse_id', 'site', 'publisher_is_draft'))", 'object_name': 'Page'}, |
||||
'application_namespace': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), |
||||
'application_urls': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '200', 'null': 'True', 'blank': 'True'}), |
||||
'changed_by': ('django.db.models.fields.CharField', [], {'max_length': '70'}), |
||||
'changed_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
||||
'created_by': ('django.db.models.fields.CharField', [], {'max_length': '70'}), |
||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'in_navigation': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), |
||||
'is_home': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), |
||||
'languages': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), |
||||
'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), |
||||
'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), |
||||
'limit_visibility_in_menu': ('django.db.models.fields.SmallIntegerField', [], {'default': 'None', 'null': 'True', 'db_index': 'True', 'blank': 'True'}), |
||||
'login_required': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
||||
'navigation_extenders': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '80', 'null': 'True', 'blank': 'True'}), |
||||
'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['cms.Page']"}), |
||||
'placeholders': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['cms.Placeholder']", 'symmetrical': 'False'}), |
||||
'publication_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), |
||||
'publication_end_date': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'}), |
||||
'publisher_is_draft': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), |
||||
'publisher_public': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'publisher_draft'", 'unique': 'True', 'null': 'True', 'to': "orm['cms.Page']"}), |
||||
'reverse_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '40', 'null': 'True', 'blank': 'True'}), |
||||
'revision_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), |
||||
'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), |
||||
'site': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'djangocms_pages'", 'to': u"orm['sites.Site']"}), |
||||
'soft_root': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), |
||||
'template': ('django.db.models.fields.CharField', [], {'default': "'INHERIT'", 'max_length': '100'}), |
||||
'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}), |
||||
'xframe_options': ('django.db.models.fields.IntegerField', [], {'default': '0'}) |
||||
}, |
||||
'cms.placeholder': { |
||||
'Meta': {'object_name': 'Placeholder'}, |
||||
'default_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'slot': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) |
||||
}, |
||||
u'contenttypes.contenttype': { |
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, |
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) |
||||
}, |
||||
u'djangocms_forms.form': { |
||||
'Meta': {'object_name': 'Form'}, |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) |
||||
}, |
||||
u'djangocms_forms.formdefinition': { |
||||
'Meta': {'object_name': 'FormDefinition', '_ormbases': ['cms.CMSPlugin']}, |
||||
u'cmsplugin_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['cms.CMSPlugin']", 'unique': 'True', 'primary_key': 'True'}), |
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
||||
'email_from': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'blank': 'True'}), |
||||
'email_subject': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
||||
'email_to': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
||||
'email_uploaded_files': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
||||
'external_redirect': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}), |
||||
'form_template': ('django.db.models.fields.CharField', [], {'default': "'djangocms_forms/form_template/default.html'", 'max_length': '150', 'blank': 'True'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
||||
'page_redirect': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['cms.Page']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), |
||||
'plugin_reference': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'plugin'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['djangocms_forms.Form']"}), |
||||
'post_submit_msg': ('django.db.models.fields.TextField', [], {'default': "u'Thank You'", 'blank': 'True'}), |
||||
'save_data': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), |
||||
'spam_protection': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), |
||||
'submit_btn_txt': ('django.db.models.fields.CharField', [], {'default': "u'Submit'", 'max_length': '100'}), |
||||
'success_redirect': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), |
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '150', 'blank': 'True'}) |
||||
}, |
||||
u'djangocms_forms.formfield': { |
||||
'Meta': {'ordering': "('position',)", 'object_name': 'FormField'}, |
||||
'choice_values': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
||||
'field_type': ('django.db.models.fields.CharField', [], {'default': "'text'", 'max_length': '100'}), |
||||
'form': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'fields'", 'to': u"orm['djangocms_forms.FormDefinition']"}), |
||||
'help_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'initial': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), |
||||
'label': ('django.db.models.fields.CharField', [], {'max_length': '255'}), |
||||
'placeholder_text': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), |
||||
'position': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}), |
||||
'required': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) |
||||
}, |
||||
u'djangocms_forms.formsubmission': { |
||||
'Meta': {'ordering': "('-creation_date',)", 'object_name': 'FormSubmission'}, |
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True'}), |
||||
'creation_date': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), |
||||
'form_data': ('jsonfield.fields.JSONField', [], {}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True', 'blank': 'True'}), |
||||
'plugin': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'submissions'", 'to': u"orm['djangocms_forms.Form']"}), |
||||
'referrer': ('django.db.models.fields.CharField', [], {'max_length': '150', 'blank': 'True'}) |
||||
}, |
||||
u'sites.site': { |
||||
'Meta': {'ordering': "(u'domain',)", 'object_name': 'Site', 'db_table': "u'django_site'"}, |
||||
'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), |
||||
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), |
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) |
||||
} |
||||
} |
||||
|
||||
complete_apps = ['djangocms_forms'] |
||||
@ -0,0 +1,43 @@ |
||||
.inline-related { |
||||
border: 1px solid #CCC; |
||||
border-radius: 2px; |
||||
box-shadow: 0 1px 2px 1px rgba(0, 0, 0, 0.08); |
||||
} |
||||
|
||||
.inline-related h3 { |
||||
background-color: #FCFCFC; |
||||
padding: 10px 20px 10px 3px; |
||||
margin: 0; |
||||
text-align: center; |
||||
} |
||||
|
||||
.inline-related .inline_label { |
||||
font-weight: 300; |
||||
} |
||||
|
||||
|
||||
.inline-related .collapse-expand { |
||||
display: inline-block; |
||||
text-transform: uppercase; |
||||
padding: 0px 10px 0px 10px; |
||||
width: 44px; |
||||
float: left; |
||||
text-decoration:none!important; |
||||
} |
||||
|
||||
.add-row { |
||||
height: 26px; |
||||
line-height: 26px; |
||||
text-align: center; |
||||
border: 1px dashed #CCC!important; |
||||
border-radius: 2px; |
||||
margin: 0; |
||||
} |
||||
|
||||
.add-row a { |
||||
display: block; |
||||
padding-left: 14px; |
||||
text-indent: -9999px; |
||||
font-size: 11px; |
||||
background-position: 50% 50%!important; |
||||
} |
||||
@ -0,0 +1,53 @@ |
||||
(function($) { |
||||
$(function() { |
||||
var formsubmission_form = $('#formsubmission_form'); |
||||
var form_field = $('select[id="id_form"]', formsubmission_form); |
||||
var headers_field = $('.field-headers', formsubmission_form); |
||||
|
||||
var loadHeaders = function() { |
||||
if (!form_field.val()) { |
||||
headers_field.slideUp('fast'); |
||||
return; |
||||
} |
||||
|
||||
var data = { |
||||
'csrfmiddlewaretoken': $('input[name="csrfmiddlewaretoken"]').val(), |
||||
'form': $('select[name="form"]').val(), |
||||
'file_type': $('select[name="file_type"]').val() |
||||
}; |
||||
|
||||
$.ajax({ |
||||
type: 'POST', |
||||
url: '', |
||||
data: data, |
||||
success: function(response) { |
||||
if (response.reloadBrowser) { |
||||
location.reload(); |
||||
} |
||||
$('select[id="id_headers_to"]').find('option').remove(); |
||||
|
||||
var headers_input_from = $('select[id="id_headers_from"]'); |
||||
headers_input_from.find('option').remove(); |
||||
|
||||
$.each(response.availableHeaders, function(index, value) { |
||||
headers_input_from.append('<option value=' + value + '>' + value + '</option>'); |
||||
}); |
||||
|
||||
SelectBox.init('id_headers_from'); |
||||
SelectBox.init('id_headers_to'); |
||||
headers_field.slideDown(); |
||||
}, |
||||
error: function() { |
||||
alert('We\'re sorry. Something unexpected happened. Please reload the page and try again.'); |
||||
} |
||||
}); |
||||
|
||||
return false; |
||||
}; |
||||
|
||||
if ($('body').hasClass('export-form')) { |
||||
loadHeaders(); |
||||
form_field.change(loadHeaders); |
||||
} |
||||
}); |
||||
})(django.jQuery); |
||||
@ -0,0 +1,66 @@ |
||||
/* |
||||
Makes all inline forms collapsible. |
||||
*/ |
||||
|
||||
jQuery(function($) { |
||||
$.makeCollapsible = function(target, item, collapsible, triggerTarget, setInitStatus, setFirstStatus) |
||||
{ |
||||
var triggerExpand = 'Edit'; |
||||
var triggerCollapse = 'Done'; |
||||
var triggerClass = 'collapse-expand'; |
||||
var triggerLink = '<a class="' + triggerClass + '" href="javascript:void(0);"></a>'; |
||||
|
||||
$(target).find(item).each(function(i) { |
||||
if ($(this).data('isCollapsible')) return; |
||||
$(this).data('isCollapsible', true); |
||||
|
||||
$(this).find(collapsible).hide(); |
||||
|
||||
// trigger already exists if created with "Add another" link
|
||||
var trigger = $(this).find(triggerTarget).find('.'+triggerClass); |
||||
if (!trigger.length) { |
||||
trigger = $(triggerLink); |
||||
$(this).find(triggerTarget).html(trigger); |
||||
} |
||||
|
||||
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('slow'); |
||||
} else { |
||||
trigger.html(triggerExpand); |
||||
$(item).find(collapsible).slideUp('slow'); |
||||
} |
||||
} |
||||
|
||||
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) |
||||
}); |
||||
}; |
||||
|
||||
var init = function() { |
||||
$.makeCollapsible('div.inline-group', 'div.inline-related', 'fieldset', 'h3 b'); |
||||
}; |
||||
init(); |
||||
// init again when "Add another" link is clicked
|
||||
$('.add-row a').click(function() { |
||||
init(); |
||||
}) |
||||
}); |
||||
@ -0,0 +1,95 @@ |
||||
/* |
||||
Enables repositioning of all inline elements by drag & drop. |
||||
|
||||
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 instances can then be ordered by that "position" field. |
||||
*/ |
||||
|
||||
jQuery(function($) { |
||||
|
||||
var positionField = 'position'; |
||||
var target = $('div.inline-group#fields-group'); |
||||
var handle = 'h3'; |
||||
var item = 'div.inline-related'; |
||||
var positionInput = 'input[id$=-'+positionField+']'; |
||||
var hidePositionFieldClosest = '.form-row'; |
||||
|
||||
var renumberAll = function() { |
||||
var pos = 1; |
||||
target.find(item).each(function(i) { |
||||
$(this).removeClass('odd even').addClass(i % 2 ? 'even' : 'odd'); |
||||
if ($(this).find(positionInput).val() != '') { |
||||
$(this).find(positionInput).val(pos); |
||||
pos++; |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
var init = function() { |
||||
target.find(item).each(function(i) { |
||||
if ($(this).data('isSortable')) return; |
||||
$(this).data('isSortable', true); |
||||
$(this).find(handle).css('cursor', 'ns-resize'); |
||||
$(this).find(handle).addClass('draggable'); |
||||
$(this).find(positionInput).each(function() { |
||||
if (hidePositionFieldClosest) { |
||||
var hidden =$('<input type="hidden" id="'+this.id+'" name="'+this.name+'" />'); |
||||
hidden.val($(this).val()); |
||||
$(this).closest(hidePositionFieldClosest).replaceWith(hidden); |
||||
} |
||||
}); |
||||
$(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 addRow = target.find('.add-row'); |
||||
addRow.remove(); |
||||
var ordered = []; |
||||
var unordered = []; |
||||
// Initially, remove and re-append all inlines ordered by their "position" value
|
||||
target.find(item).each(function(i) { |
||||
var initialPos = $(this).find(positionInput).val(); |
||||
if (initialPos) { |
||||
while (initialPos < ordered.length && ordered[initialPos]) { |
||||
initialPos++; |
||||
} |
||||
ordered[initialPos] = this; |
||||
} else { |
||||
unordered[unordered.length] = this; |
||||
} |
||||
$(this).removeClass('odd even').addClass(i % 2 ? 'even' : 'odd'); |
||||
this.parentElement.removeChild(this); |
||||
}); |
||||
for (var i = 0; i < ordered.length; i++) { |
||||
var el = ordered[i]; |
||||
if (el) { |
||||
target.append(el); |
||||
} |
||||
} |
||||
// Add "position"-less elements in the end
|
||||
for (var i = 0; i < unordered.length; i++) { |
||||
var el = unordered[i]; |
||||
target.append(el); |
||||
} |
||||
target.append(addRow); |
||||
|
||||
target.sortable({ |
||||
containment: 'parent', |
||||
items: item, |
||||
handle: handle, |
||||
update: renumberAll, |
||||
revert: true, |
||||
opacity: 0.9 |
||||
}); |
||||
|
||||
init(); |
||||
// init again when "Add another" link is clicked
|
||||
$('.add-row a').click(function() { |
||||
init(); |
||||
}); |
||||
|
||||
}); |
||||
@ -0,0 +1,45 @@ |
||||
/* |
||||
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 target = $('div.inline-group'); |
||||
var item = 'div.inline-related'; |
||||
var nameInput = 'input[id*=-label]'; |
||||
var typeInput = 'select[id*=-field_type]'; |
||||
|
||||
|
||||
var init = function() { |
||||
target.find(item).each(function() { |
||||
var nameField = $(this).find(nameInput); |
||||
var typeField = $(this).find(typeInput); |
||||
var label = $('.inline_label', this); |
||||
var rename = function() { |
||||
if (nameField.val()) { |
||||
label.text(nameField.val()); |
||||
} |
||||
else { |
||||
label.text(typeField.find('option:selected').text() + ' Field'); |
||||
} |
||||
}; |
||||
nameField.keyup(function(event) { |
||||
// Update name while typing
|
||||
rename(); |
||||
}); |
||||
|
||||
typeField.change(function(){ |
||||
rename(); |
||||
}); |
||||
rename(); |
||||
}) |
||||
} |
||||
|
||||
init(); |
||||
|
||||
// init again when "Add another" link is clicked
|
||||
$('.add-row a').click(function() { |
||||
init(); |
||||
}) |
||||
}); |
||||
@ -0,0 +1,35 @@ |
||||
jQuery(function($) { |
||||
var target = $('div.inline-group'); |
||||
var item = 'div.inline-related'; |
||||
var typeInput = 'select[id*=-field_type]'; |
||||
|
||||
var init = function() { |
||||
target.find(item).each(function() { |
||||
var that = $(this); |
||||
var inputField = that.find(typeInput); |
||||
var toggleField = function() { |
||||
var selectedFieldType = inputField.val(); |
||||
var choiceFields = ['checkbox_multiple', 'select', 'radio', 'file']; |
||||
var showChoiceField = $.inArray(selectedFieldType, choiceFields) >= 0; |
||||
that.find('.field-choice_values') |
||||
.toggle(showChoiceField) |
||||
.toggleClass('required', showChoiceField); |
||||
|
||||
that.find('.field-placeholder_text, .field-help_text, .field-choice_values:not(:hidden)') |
||||
.toggle(selectedFieldType != 'hidden') |
||||
|
||||
}; |
||||
inputField.change(function() { |
||||
toggleField(); |
||||
}); |
||||
toggleField(); |
||||
}) |
||||
}; |
||||
|
||||
init(); |
||||
|
||||
// init again when "Add another" link is clicked
|
||||
$('.add-row a').click(function() { |
||||
init(); |
||||
}) |
||||
}); |
||||
@ -0,0 +1,100 @@ |
||||
(function ($) { |
||||
$.fn.djangocms_forms = function (options) { |
||||
if (options === undefined) { options = {}; } |
||||
|
||||
var defaults = { |
||||
form_wrapper: '.form-wrapper', |
||||
field_wrapper: '.field-wrapper', |
||||
form_errors: '.form-errors', |
||||
field_errors: '.field-errors', |
||||
form_success: '.form-success', |
||||
errorlist_css_class: 'errorlist', |
||||
error_css_class: 'error', |
||||
server_error: 'We\'re sorry. Something Unexpected Happened. Please Try Again Later.' |
||||
}; |
||||
|
||||
this.each(function(options) { |
||||
var options = $.extend( {}, defaults, options) ; |
||||
|
||||
var el = $(this), |
||||
form_wrapper = $(options.form_wrapper, el), |
||||
form_success = $(options.form_success, el), |
||||
form = $('form', form_wrapper); |
||||
|
||||
|
||||
function clearErrors() { |
||||
form.find(options.form_errors).fadeOut().empty(); //clear form errors
|
||||
form.find(options.field_errors).fadeOut().empty(); //clear field errors
|
||||
form.find(options.field_wrapper).removeClass(options.error_css_class); //remove error classes
|
||||
} |
||||
|
||||
// post-submit callback
|
||||
function ajaxSuccess(response) { |
||||
if (response.status == 'success'){ |
||||
formValid(response.redirect_url); |
||||
} |
||||
else if (response.status == 'error'){ |
||||
formInvalid(response.form_errors); |
||||
} |
||||
} |
||||
|
||||
function formValid(success_url) { |
||||
form_success.fadeIn('slow'); |
||||
form_wrapper.slideUp('slow').remove(); |
||||
|
||||
if (success_url){ |
||||
setTimeout(function(){ |
||||
window.location = success_url; |
||||
}, 1000); |
||||
} |
||||
|
||||
} |
||||
|
||||
function formInvalid(form_errors) { |
||||
clearErrors() |
||||
$.each(form_errors, function(key, value) { |
||||
var field = form.find(':input[name=' + key + ']').first(); |
||||
var field_wrapper = field.parents(options.field_wrapper).addClass(options.error_css_class); |
||||
var field_error = $('<ul/>').addClass(options.errorlist_css_class); |
||||
$.each(value, function(key, value) { |
||||
$('<li>', { |
||||
text: value |
||||
}).appendTo(field_error); |
||||
}); |
||||
field_wrapper.find(options.field_errors).append(field_error).fadeIn('slow') |
||||
}); |
||||
if (form_errors.__all__) { |
||||
var form_error = $('<ul/>').addClass(options.errorlist_css_class); |
||||
$.each(form_errors.__all__, function(key, value) { |
||||
$('<li>', { |
||||
text: value |
||||
}).appendTo(form_error); |
||||
}); |
||||
form.find(options.form_errors).append(form_error).fadeIn('slow'); |
||||
} |
||||
} |
||||
|
||||
function ajaxError() { |
||||
clearErrors(); |
||||
var form_error = $('<ul/>').addClass(options.error_class); |
||||
$('<li>', { |
||||
html: options.server_error |
||||
}).appendTo(form_error); |
||||
form.find(options.form_errors).append(form_error).fadeIn('slow'); |
||||
} |
||||
|
||||
// attach handler to form's submit event
|
||||
form.submit(function () { |
||||
var ajaxOptions = { |
||||
type: 'POST', |
||||
success: ajaxSuccess, |
||||
error: ajaxError |
||||
}; |
||||
// submit the form
|
||||
$(this).ajaxSubmit(ajaxOptions); |
||||
// return false to prevent normal browser submit and page navigation
|
||||
return false; |
||||
}); |
||||
}); |
||||
}; |
||||
})(jQuery); |
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,26 @@ |
||||
{% extends "admin/change_form.html" %} |
||||
{% load i18n djangocms_forms_tags %} |
||||
|
||||
{% block submit_buttons_bottom %}{% endblock %} |
||||
|
||||
{% block object-tools-items %} |
||||
{% endblock %} |
||||
|
||||
|
||||
{% block field_sets %} |
||||
{% if original.form_data %} |
||||
<fieldset class="module aligned"> |
||||
{% for field in original.form_data %} |
||||
<div class="form-row required {{ field.name}}"> |
||||
<div> |
||||
<label> |
||||
{{ field.label }}: |
||||
</label> |
||||
<p>{{ field|to_html }}</p> |
||||
</div> |
||||
</div> |
||||
{% endfor %} |
||||
</fieldset> |
||||
{% endif %} |
||||
{{ block.super }} |
||||
{% endblock %} |
||||
@ -0,0 +1,15 @@ |
||||
{% extends "admin/change_list.html" %} |
||||
{% load i18n admin_urls %} |
||||
|
||||
{% block object-tools %} |
||||
{% if has_export_permission %} |
||||
<ul class="object-tools"> |
||||
{% block object-tools-items %} |
||||
<li> |
||||
{% url cl.opts|admin_urlname:'export' as export_url %} |
||||
<a href="{{ export_url }}" class="addlink">{% trans "Export Form Submissions" %}</a> |
||||
</li> |
||||
{% endblock %} |
||||
</ul> |
||||
{% endif %} |
||||
{% endblock %} |
||||
@ -0,0 +1,22 @@ |
||||
{% extends "admin/change_form.html" %} |
||||
{% load i18n admin_urls admin_static admin_modify %} |
||||
|
||||
{% block bodyclass %}{{ block.super }} export-form{% endblock %} |
||||
|
||||
{% if not is_popup %} |
||||
{% block breadcrumbs %} |
||||
<div class="breadcrumbs"> |
||||
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a> |
||||
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a> |
||||
› {% if has_change_permission %}<a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %} |
||||
› {% trans 'Export' %} {{ opts.verbose_name|capfirst }} |
||||
</div> |
||||
{% endblock %} |
||||
{% endif %} |
||||
|
||||
{% block submit_buttons_bottom %} |
||||
<div class="submit-row"> |
||||
<input type="submit" value="{% trans 'Export' %}" class="default" name="export" /> |
||||
</div> |
||||
{% endblock %} |
||||
|
||||
@ -0,0 +1,19 @@ |
||||
{% load i18n djangocms_forms_tags %}<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> |
||||
<html xmlns="http://www.w3.org/1999/xhtml"> |
||||
<head> |
||||
<meta name="viewport" content="width=device-width" /> |
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> |
||||
<title>{{ title }}</title> |
||||
</head> |
||||
<body itemscope itemtype="http://schema.org/EmailMessage"> |
||||
<h1>{{ form.title }}</h1> |
||||
<p><strong>{% trans 'Date:' %}</strong> {% now 'jS F Y H:i' %}</p> |
||||
{% for item in form_data %} |
||||
<p> |
||||
<strong>{% if item.label %}{{ item.label }}{% else %}{{ item.name }}{% endif %}:</strong><br/> |
||||
{{ item.value|friendly|default_if_none:'—' }} |
||||
</p> |
||||
{% endfor %} |
||||
{% trans 'Sent via' %} <a href="http://{{ request.get_host }}{{ referrer }}">{{ request.get_host }}{{ referrer }}</a> |
||||
</body> |
||||
</html> |
||||
@ -0,0 +1,11 @@ |
||||
{% load i18n djangocms_forms_tags %} |
||||
|
||||
{% trans 'Form:' %} {{ form.name }} |
||||
{% trans 'Date:' %} {% now "jS F Y H:i" %} |
||||
|
||||
{% for item in form_data %} |
||||
{% if item.label %}{{ item.label }}{% else %}{{ item.name }}{% endif %}: |
||||
{{ item.value|friendly|default_if_none:'—' }} |
||||
{% endfor %} |
||||
|
||||
{% trans 'Sent via' %} http://{{ request.get_host }} |
||||
@ -0,0 +1,61 @@ |
||||
{% load cms_tags djangocms_forms_tags i18n sekizai_tags staticfiles %} |
||||
<div class="forms" id="{{ instance.form_id }}"> |
||||
{% if instance.title %} |
||||
<h3 class="title">{{ instance.title }}</h3> |
||||
{% endif %} |
||||
{% if instance.description %} |
||||
<div class="description"> |
||||
{{ instance.description|safe }} |
||||
</div> |
||||
{% endif %} |
||||
<div class="form-wrapper"> |
||||
<form action="{{ form.submission_url }}" method="POST" enctype="multipart/form-data"> |
||||
<div class="form-errors" style="display:none;"></div> |
||||
{% for field in form.visible_fields %} |
||||
<div class="field-wrapper {{ field|input_class }} {{ field.css_classes }}"> |
||||
<div class="field-errors" style="display:none;"></div> |
||||
{% if field|is_checkbox %} |
||||
{{ field }} |
||||
{% endif %} |
||||
<label for="{{ field.id_for_label }}"> |
||||
{{ field.label }} |
||||
</label> |
||||
{% if not field|is_checkbox %} |
||||
<p>{{ field }}</p> |
||||
{% endif %} |
||||
{% if field.help_text %} |
||||
<div id="help-text-{{ field.auto_id }}" class="help-text">{{ field.help_text|safe }}</div> |
||||
{% endif %} |
||||
</div> |
||||
{% endfor %} |
||||
{% csrf_token %} |
||||
{% for hidden in form.hidden_fields %} |
||||
{{ hidden }} |
||||
{% endfor %} |
||||
<div class="button-wrapper submit"> |
||||
<button class="form-button btn btn-primary" type="submit" value="{{ instance.submit_btn_txt }}">{{ instance.submit_btn_txt }}</button> |
||||
</div> |
||||
</form> |
||||
</div> |
||||
<div class="form-success" style="display: none;"> |
||||
{% if instance.post_submit_msg %} |
||||
<p>{{ instance.post_submit_msg|safe }}</p> |
||||
{% else %} |
||||
{% blocktrans %} |
||||
<h3>Submission successful</h3> |
||||
<p>Thank You! Your form has been successfully submitted!</p> |
||||
{% endblocktrans %} |
||||
{% endif %} |
||||
</div> |
||||
</div> |
||||
|
||||
{% addtoblock "js" %} |
||||
<script src="{% static 'js/djangocms_forms/libs/jquery.form.min.js' %}"></script> |
||||
<script src="{% static 'js/djangocms_forms/djangocms_forms.js' %}"></script> |
||||
<script type="application/javascript"> |
||||
$(function() { |
||||
$('.forms').djangocms_forms(); |
||||
}); |
||||
</script> |
||||
{% endaddtoblock %} |
||||
|
||||
@ -0,0 +1,89 @@ |
||||
from django import forms, template |
||||
from django.template.defaultfilters import yesno |
||||
from django.utils.safestring import mark_safe |
||||
from django.utils.six import string_types |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
|
||||
register = template.Library() |
||||
|
||||
|
||||
@register.filter |
||||
def is_checkbox(field): |
||||
return isinstance(field.field.widget, forms.CheckboxInput) |
||||
|
||||
|
||||
@register.filter |
||||
def is_password(field): |
||||
return isinstance(field.field.widget, forms.PasswordInput) |
||||
|
||||
|
||||
@register.filter |
||||
def is_radioselect(field): |
||||
return isinstance(field.field.widget, forms.RadioSelect) |
||||
|
||||
|
||||
@register.filter |
||||
def is_select(field): |
||||
return isinstance(field.field.widget, forms.Select) |
||||
|
||||
|
||||
@register.filter |
||||
def is_checkboxselectmultiple(field): |
||||
return isinstance(field.field.widget, forms.CheckboxSelectMultiple) |
||||
|
||||
|
||||
@register.filter |
||||
def is_file(field): |
||||
return isinstance(field.field.widget, forms.ClearableFileInput) |
||||
|
||||
|
||||
@register.filter |
||||
def is_required(field): |
||||
return field.field.required |
||||
|
||||
|
||||
@register.filter |
||||
def classes(field): |
||||
""" |
||||
Returns CSS classes of a field |
||||
""" |
||||
return field.widget.attrs.get('class', None) |
||||
|
||||
|
||||
@register.filter |
||||
def input_class(field): |
||||
""" |
||||
Returns widgets class name in lowercase |
||||
""" |
||||
return field.field.widget.__class__.__name__.lower() |
||||
|
||||
|
||||
@register.filter |
||||
def friendly(value): |
||||
if value in (None, '', [], (), {}): |
||||
return None |
||||
|
||||
if type(value) is list: |
||||
value = ', '.join(value) |
||||
if type(value) is bool: |
||||
value = yesno(value, u'{0},{1}'.format(_('Yes'), _('No'))) |
||||
if not isinstance(value, string_types): |
||||
value = unicode(value) |
||||
return value |
||||
|
||||
|
||||
@register.filter |
||||
def to_html(field): |
||||
value = field['value'] |
||||
field_type = field['type'] |
||||
|
||||
if value in (None, '', [], (), {}): |
||||
return mark_safe('—') |
||||
|
||||
if field_type == 'file': |
||||
value = '<a href="{0}">{0}</a>'.format(value) |
||||
if field_type == 'checkbox': |
||||
value = yesno(bool(value), u'{0},{1}'.format(_('Yes'), _('No'))) |
||||
if field_type == 'checkbox_multiple': |
||||
value = ', '.join(list(value)) |
||||
return mark_safe(value) |
||||
@ -0,0 +1,62 @@ |
||||
import hashlib |
||||
import os |
||||
import uuid |
||||
|
||||
from django.core.files.base import File |
||||
from django.core.files.storage import get_storage_class |
||||
from django.db.models.fields.files import FieldFile |
||||
from django.utils.functional import LazyObject |
||||
|
||||
from .conf import settings |
||||
|
||||
|
||||
class FileStorage(LazyObject): |
||||
def _setup(self): |
||||
self._wrapped = get_storage_class(settings.DJANGOCMS_FORMS_FILE_STORAGE)() |
||||
|
||||
file_storage = FileStorage() |
||||
|
||||
|
||||
def handle_uploaded_files(form): |
||||
files = [] |
||||
if len(form.file_fields): |
||||
secret_hash = hashlib.sha1(str(uuid.uuid4())).hexdigest() |
||||
for field in form.file_fields: |
||||
uploaded_file = form.cleaned_data.get(field, None) |
||||
if uploaded_file is None: |
||||
continue |
||||
valid_file_name = file_storage.get_valid_name(uploaded_file.name) |
||||
root, ext = os.path.splitext(valid_file_name) |
||||
filename = file_storage.get_available_name(os.path.join( |
||||
settings.DJANGOCMS_FORMS_FILE_STORAGE_DIR, |
||||
form.form_definition.upload_to, |
||||
'%s_%s%s' % (root, secret_hash, ext))) |
||||
file_storage.save(filename, uploaded_file) |
||||
form.cleaned_data[field] = StoredUploadedFile(filename) |
||||
files.append(file_storage.path(filename)) |
||||
return files |
||||
|
||||
|
||||
class StoredUploadedFile(FieldFile): |
||||
""" |
||||
A wrapper for uploaded files that is compatible to the FieldFile class, i.e. |
||||
you can use instances of this class in templates just like you use the value |
||||
of FileFields (e.g. `{{ my_file.url }}`) |
||||
""" |
||||
|
||||
def __init__(self, name): |
||||
File.__init__(self, None, name) |
||||
self.field = self |
||||
|
||||
@property |
||||
def storage(self): |
||||
return file_storage |
||||
|
||||
def save(self, *args, **kwargs): |
||||
raise NotImplementedError('Static files are read-only') |
||||
|
||||
def delete(self, *args, **kwargs): |
||||
raise NotImplementedError('Static files are read-only') |
||||
|
||||
def __unicode__(self): |
||||
return self.name |
||||
@ -0,0 +1,8 @@ |
||||
from django.conf.urls import patterns, url |
||||
|
||||
from .views import FormSubmission |
||||
|
||||
urlpatterns = patterns( |
||||
'', |
||||
url(r'^forms/submit/$', FormSubmission.as_view(), name='djangocms_forms_submissions'), |
||||
) |
||||
@ -0,0 +1,17 @@ |
||||
from hashids import Hashids |
||||
|
||||
from .conf import settings |
||||
|
||||
|
||||
def int_to_hashid(i, min_length=30, salt=settings.DJANGOCMS_FORMS_HASHIDS_SALT): |
||||
hashids = Hashids(salt, min_length=min_length) |
||||
return hashids.encode(i) |
||||
|
||||
|
||||
def hashid_to_int(hashid, min_length=30, salt=settings.DJANGOCMS_FORMS_HASHIDS_SALT): |
||||
hashids = Hashids(salt, min_length=min_length) |
||||
|
||||
try: |
||||
return hashids.decode(hashid)[0] |
||||
except IndexError: |
||||
pass |
||||
@ -0,0 +1,61 @@ |
||||
# -*- coding: utf-8 -*- |
||||
|
||||
from django.contrib import messages |
||||
from django.shortcuts import get_object_or_404, redirect |
||||
from django.utils.html import strip_tags |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
from django.views.generic import FormView |
||||
|
||||
from .forms import FormBuilder |
||||
from .models import FormDefinition |
||||
from .signals import form_submission |
||||
from .uploads import handle_uploaded_files |
||||
from .utils import hashid_to_int |
||||
|
||||
try: |
||||
from django.http import JsonResponse |
||||
except ImportError: |
||||
from .compat import JsonResponse |
||||
|
||||
|
||||
class FormSubmission(FormView): |
||||
form_class = FormBuilder |
||||
http_method_names = ['post'] |
||||
|
||||
def get_form_kwargs(self): |
||||
form_kwargs = super(FormSubmission, self).get_form_kwargs() |
||||
form_id = hashid_to_int(self.request.POST.get('form_id')) |
||||
form_definition = get_object_or_404(FormDefinition, pk=form_id) |
||||
form_kwargs.update({ |
||||
'form_definition': form_definition |
||||
}) |
||||
return form_kwargs |
||||
|
||||
def form_valid(self, form, *args, **kwargs): |
||||
form.files = handle_uploaded_files(form) |
||||
form.save(request=self.request) |
||||
form_submission.send( |
||||
sender=self.__class__, |
||||
form=form.form_definition, |
||||
cleaned_data=form.cleaned_data) |
||||
|
||||
if self.request.is_ajax(): |
||||
response = { |
||||
'status': 'success', |
||||
'redirect_url': form.redirect_url |
||||
} |
||||
return JsonResponse(response) |
||||
else: |
||||
messages.success(self.request, strip_tags(form.form_definition.post_submit_msg)) |
||||
return redirect(form.redirect_url or form.cleaned_data['current_page']) |
||||
|
||||
def form_invalid(self, form, *args, **kwargs): |
||||
if self.request.is_ajax(): |
||||
response = { |
||||
'status': 'error', |
||||
'form_errors': form.errors, |
||||
} |
||||
return JsonResponse(response) |
||||
else: |
||||
messages.error(self.request, _(u'Invalid form data, one or more fields had errors')) |
||||
return redirect(form.cleaned_data['current_page']) |
||||
@ -0,0 +1,17 @@ |
||||
from django.forms.widgets import TextInput |
||||
|
||||
|
||||
class TelephoneInput(TextInput): |
||||
input_type = 'tel' |
||||
|
||||
|
||||
class SearchInput(TextInput): |
||||
input_type = 'search' |
||||
|
||||
|
||||
class DateInput(TextInput): |
||||
input_type = 'date' |
||||
|
||||
|
||||
class TimeInput(TextInput): |
||||
input_type = 'time' |
||||
@ -0,0 +1,14 @@ |
||||
{% load i18n cms_tags menu_tags cache %} |
||||
{{ children.count }} |
||||
{% if children|length > 1 %} |
||||
<ul class="list-unstyled"> |
||||
{% for child in children %} |
||||
<li {% if child.selected %}class="active"{% endif %}> |
||||
<a href="{{ child.get_absolute_url }}"><span>{{ child.get_menu_title }}</span></a> |
||||
</li> |
||||
{% if class and forloop.last and not forloop.parentloop %}{% endif %} |
||||
{% endfor %} |
||||
</ul> |
||||
{% else %} |
||||
{% static_placeholder 'footer_promo' %} |
||||
{% endif %} |
||||
Loading…
Reference in new issue