#!/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 or self.form_definition.id) if request.session.get(form_slug, False): try: form_instance = FormSubmission.objects.get(id=request.session.get(form_slug, False)) # Удаляем сессию, если это последний шаг. Признак последнего шага for data in form_data: if data['name'] == 'clear_session': request.session[form_slug] = None form_instance = None if form_instance: form_instance.form_data += form_data form_instance.save() 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): # Отправляем, только если конец сессии # Если есть сессия для этой формы, то сохраняем в неё. form_slug = "form-%d" % (self.form_definition.plugin_reference.id or self.form_definition.id) if request.session.get(form_slug, False): try: form_instance = FormSubmission.objects.get(id=request.session.get(form_slug, False)) form_data = form_instance.form_data except (FormSubmission.DoesNotExist): form_instance = None pass send = False client_mail_to = None for data in form_data: if data['name'] == 'end_session': request.session[form_slug] = None request.session['last-completed-form-id'] = form_instance.id send = True if data['type'] == 'email': client_mail_to = data['value'] if not send: return # Send to company 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 \ u'%s №%d – заявка с сайта' % (self.form_definition.name, form_instance.id) 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) # Send to client if client_mail_to: mail_to = re.compile('\s*[,;]+\s*').split(client_mail_to) mail_from = self.form_definition.email_from or None mail_subject = self.form_definition.email_subject or \ u'%s. Обращение №%d' % (self.form_definition.name, form_instance.id) context = { 'form': self.form_definition, 'referrer': referrer, 'title': mail_subject, 'form_data': form_data, 'request': request, 'recipients': mail_to, 'form_instance': form_instance } message = render_to_string('djangocms_forms/email_template/client_email.txt', context) message_html = render_to_string('djangocms_forms/email_template/client_email.html', context) email = EmailMultiAlternatives(mail_subject, message, mail_from, mail_to) email.attach_alternative(message_html, 'text/html') 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)