Commits

Anonymous committed 78a8130

initial checkin of version .1

Comments (0)

Files changed (11)

+django-email forms is an application for creating multi input forms,
+with no default fields, and an option for emailing entries to a 
+supplied list of entries. Borrows from django-crowdsourcing,
+http://code.google.com/p/django-crowdsourcing/  
+
+No requirements, though the usage of django-positions 
+http://code.google.com/p/django-positions/ is optional.

Empty file added.

emailform/admin.py

+from __future__ import absolute_import
+
+import re
+
+from django.contrib import admin
+from django.forms import ModelForm, ValidationError
+from django.utils.translation import ugettext_lazy as _
+
+from .models import (Question, ContactForm, Entry)
+
+
+class QuestionInline(admin.StackedInline):
+    model = Question
+    extra = 3
+
+
+class ContactFormAdminForm(ModelForm):
+
+    class Meta:
+        model = ContactForm
+
+
+class ContactFormAdmin(admin.ModelAdmin):
+    save_as = True
+    form = ContactFormAdminForm
+    search_fields = ('title', 'slug', 'description')
+    prepopulated_fields = {'slug': ('title',)}
+    list_display = (
+        'title',
+        'slug',
+        'is_published',
+        'site')
+    list_filter = ('is_published', 'site')
+    date_hierarchy = 'starts_at'
+    inlines = [QuestionInline]
+
+
+admin.site.register(ContactForm, ContactFormAdmin)
+
+
+class EntryAdmin(admin.ModelAdmin):
+    search_fields = ('content',)
+    list_display = ('contact_form', 'submitted_at',
+                    'ip_address', )
+    list_filter = ('contact_form', 'submitted_at', )
+    date_hierarchy = 'submitted_at'
+
+
+admin.site.register(Entry, EntryAdmin)

emailform/fields.py

+try:
+    from sorl.thumbnail.fields import ImageWithThumbnailsField
+except ImportError:
+
+    from django.db.models import ImageField
+
+    class ImageWithThumbnailsField(ImageField):
+
+        def __init__(self, *args, **kwargs):
+            kwargs.pop('thumbnail', None)
+            super(ImageWithThumbnailsField, self).__init__(*args, **kwargs)

emailform/forms.py

+from __future__ import absolute_import
+
+import re
+
+from django.conf import settings
+from django.contrib.localflavor.us.forms import (
+    USPhoneNumberField,
+    USStateField,
+    USZipCodeField)
+from django.forms import (
+    BooleanField,
+    CharField,
+    CheckboxSelectMultiple,
+    ChoiceField,
+    EmailField,
+    FloatField,
+    Form,
+    ImageField,
+    IntegerField,
+    MultipleChoiceField,
+    RadioSelect,
+    Select,
+    Textarea,
+    ValidationError,
+    )
+from django.forms.forms import BoundField
+
+from .models import OPTION_TYPE_CHOICES, ContactForm, Question, Entry
+
+
+class BaseAnswerForm(Form):
+
+    def __init__(self,
+                 question,
+                 entry=None,
+                 *args,
+                 **kwargs):
+        self.question = question
+        self.entry = entry
+        super(BaseAnswerForm, self).__init__(*args, **kwargs)
+        self._configure_answer_field()
+
+    def as_template(self):
+        "Helper function for fieldsting fields data from form."
+        bound_fields = [BoundField(self, field, name) \
+                      for name, field in self.fields.items()]
+        c = Context(dict(form=self, bound_fields=bound_fields))
+        t = loader.get_template('forms/form.html')
+        return t.render(c)
+
+    def _configure_answer_field(self):
+        answer = self.fields['answer']
+        q = self.question
+        answer.required = q.required
+        answer.label = q.question
+        answer.help_text = q.help_text
+        return answer
+
+
+class USPhoneNumberAnswer(BaseAnswerForm):
+    answer = USPhoneNumberField()
+
+
+class USStateAnswer(BaseAnswerForm):
+    answer = USStateField()
+
+
+class USZipCodeAnswer(BaseAnswerForm):
+    answer = USZipCodeField()
+
+
+class TextInputAnswer(BaseAnswerForm):
+    answer = CharField()
+
+
+class IntegerInputAnswer(BaseAnswerForm):
+    answer = IntegerField()
+
+
+class FloatInputAnswer(BaseAnswerForm):
+    answer = FloatField()
+
+
+class BooleanInputAnswer(BaseAnswerForm):
+    answer = BooleanField(initial=False)
+
+    def _configure_answer_field(self):
+        fld = super(BooleanInputAnswer, self)._configure_answer_field()
+        # we don't want to set this as required, as a single boolean field
+        # being required doesn't make much sense in a survey
+        fld.required = False
+        return fld
+
+    def clean_answer(self):
+        value = self.cleaned_data['answer']
+        if not value:
+            return False
+        return value
+
+
+class TextAreaAnswer(BaseAnswerForm):
+    answer = CharField(widget=Textarea)
+
+
+class EmailAnswer(BaseAnswerForm):
+    answer = EmailField()
+
+
+class VideoAnswer(BaseAnswerForm):
+    answer = CharField()
+
+
+class PhotoUpload(BaseAnswerForm):
+    answer = ImageField()
+
+
+class BaseOptionAnswer(BaseAnswerForm):
+
+    def __init__(self, *args, **kwargs):
+        super(BaseOptionAnswer, self).__init__(*args, **kwargs)
+        choices = [(x, x) for x in self.question.parsed_options]
+        if not self.question.required:
+            choices = [('', '---------',)] + choices
+        self.fields['answer'].choices = choices
+
+    def clean_answer(self):
+        key = self.cleaned_data['answer']
+        if not key and self.fields['answer'].required:
+            raise ValidationError, _('This field is required.')
+        if not isinstance(key, (list, tuple)):
+            key = (key,)
+        return key
+
+
+class OptionAnswer(BaseOptionAnswer):
+    answer = ChoiceField()
+
+
+class OptionRadio(BaseOptionAnswer):
+    answer = ChoiceField(widget=RadioSelect)
+
+
+class OptionCheckbox(BaseOptionAnswer):
+    answer = MultipleChoiceField(widget=CheckboxSelectMultiple)
+
+
+## each question gets a form with one element, determined by the type
+## for the answer.
+QTYPE_FORM = {
+    OPTION_TYPE_CHOICES.TEXT_FIELD:        TextInputAnswer,
+    OPTION_TYPE_CHOICES.INTEGER:           IntegerInputAnswer,
+    OPTION_TYPE_CHOICES.FLOAT:             FloatInputAnswer,
+    OPTION_TYPE_CHOICES.BOOLEAN:           BooleanInputAnswer,
+    OPTION_TYPE_CHOICES.TEXT_AREA:         TextAreaAnswer,
+    OPTION_TYPE_CHOICES.SELECT_ONE_CHOICE: OptionAnswer,
+    OPTION_TYPE_CHOICES.RADIO_LIST:        OptionRadio,
+    OPTION_TYPE_CHOICES.CHECKBOX_LIST:     OptionCheckbox,
+    OPTION_TYPE_CHOICES.EMAIL_FIELD:       EmailAnswer,
+    OPTION_TYPE_CHOICES.PHOTO_UPLOAD:      PhotoUpload,
+    OPTION_TYPE_CHOICES.VIDEO_LINK:        VideoAnswer,
+    OPTION_TYPE_CHOICES.PHONE_NUMBER:      USPhoneNumberAnswer,
+    OPTION_TYPE_CHOICES.STATE:             USStateAnswer,
+    OPTION_TYPE_CHOICES.ZIPCODE:           USZipCodeAnswer}
+
+
+def forms_for_contact_form(contact_form, request='testing', entry=None):
+    testing = 'testing' == request
+    post = None if testing else request.POST or None
+    files = None if testing else request.FILES or None
+    return [_form_for_question(q, entry, post, files)
+        for q in contact_form.questions.all().order_by("order")]
+
+
+def _form_for_question(question,
+                       entry=None,
+                       data=None,
+                       files=None):
+    return QTYPE_FORM[question.option_type](
+        question=question,
+        entry=entry,
+        prefix='%s_%s' % (question.contact_form.id,
+                        question.id),
+        data=data,
+        files=files)

emailform/models.py

+from __future__ import absolute_import
+
+import datetime
+from operator import itemgetter
+
+from django.contrib.sites.models import Site
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+
+from .util import ChoiceEnum
+from . import settings as local_settings
+
+try:
+    from positions.fields import PositionField
+except ImportError:
+    logging.warn('positions not installed. '
+                 'Will just use integers for position fields.')
+    PositionField = None
+
+
+class LiveContactFormManager(models.Manager):
+
+    def get_query_set(self):
+        now = datetime.datetime.now()
+        return super(LiveContactFormManager, self).get_query_set().filter(
+            is_published=True,
+            starts_at__lte=now)
+
+
+class ContactForm(models.Model):
+
+    class Meta:
+        ordering = ('-starts_at',)
+
+    title = models.CharField(max_length=80)
+    slug = models.SlugField(unique=True)
+    description = models.TextField(blank=True)
+    starts_at = models.DateTimeField(default=datetime.datetime.now)
+    is_published = models.BooleanField(default=False)
+    thanks_message = models.TextField(default="Thanks for entering!")
+    email = models.CharField(
+        blank=True,
+        help_text=("Send a notification to this email whenever someone "
+                   "submits an entry to this contact form",),
+        max_length=300)
+    site = models.ForeignKey(Site)
+
+    def __unicode__(self):
+        return self.title
+
+    @models.permalink
+    def get_absolute_url(self):
+        return ('contact_form_detail', (), {'slug': self.slug})
+
+    objects = models.Manager()
+    live = LiveContactFormManager()
+
+OPTION_TYPE_CHOICES = ChoiceEnum(sorted([('char', 'Text Field'),
+                                         ('email', 'Email Field'),
+                                         ('photo', 'Photo Upload'),
+                                         ('video', 'Video Link'),
+                                         ('location', 'Location Field'),
+                                         ('integer', 'Integer'),
+                                         ('float', 'Float'),
+                                         ('bool', 'Boolean'),
+                                         ('text', 'Text Area'),
+                                         ('select', 'Select One Choice'),
+                                         ('radio', 'Radio List'),
+                                         ('checkbox', 'Checkbox List'),
+                                         ('zipcode', 'Zipcode'),
+                                         ('state', 'State'),
+                                         ('phonenumber', 'Phone Number')],
+                                        key=itemgetter(1)))
+
+
+class Question(models.Model):
+
+    class Meta:
+        ordering = ('order',)
+
+    contact_form = models.ForeignKey(ContactForm, related_name="questions")
+    question = models.TextField(help_text=_(
+        "Appears on the contact form entry page."))
+    label = models.CharField(max_length=32, help_text=_(
+        "Appears on the results page."))
+    help_text = models.TextField(blank=True)
+    required = models.BooleanField(default=False)
+    if PositionField:
+        order = PositionField(collection=('contact_form',))
+    else:
+        order = models.IntegerField()
+    option_type = models.CharField(max_length=12, choices=OPTION_TYPE_CHOICES)
+    options = models.TextField(blank=True, default='')
+
+    def __unicode(self):
+        return self.question
+
+    @property
+    def parsed_options(self):
+        if OPTION_TYPE_CHOICES.BOOLEAN == self.option_type:
+            return [True, False]
+        return filter(None, (s.strip() for s in self.options.splitlines()))
+
+
+class Entry(models.Model):
+
+    class Meta:
+        verbose_name = "Entry"
+        verbose_name_plural = "Entries"
+        ordering = ('-submitted_at',)
+
+    contact_form = models.ForeignKey(ContactForm)
+    ip_address = models.IPAddressField()
+    submitted_at = models.DateTimeField(default=datetime.datetime.now)
+    content = models.TextField(blank=True)

emailform/settings.py

+import re
+
+from django.conf import settings as _gs
+
+
+CONTACTFORM_EMAIL_FROM = getattr(_gs,
+                            'CONTACTFORM_EMAIL_FROM',
+                            'donotreply@donotreply.com')
+
+
+CONTACTFORM_ADMIN_SITE = getattr(_gs, 'CONTACTFORM_ADMIN_SITE', '')

emailform/urls.py

+from __future__ import absolute_import
+
+from django.conf.urls.defaults import patterns, url
+
+from .views import contact_form_detail
+
+urlpatterns = patterns(
+    "",
+    url(r'^(?P<slug>[-a-z0-9_]+)/embed/$',
+        contact_form_detail,
+        {'embed' : True},
+        name="contact_form_detail"),
+
+    url(r'^(?P<slug>[-a-z0-9_]+)/$',
+        contact_form_detail,
+        name="contact_form_detail"),
+    )

emailform/util.py

+import itertools
+import re
+
+from django.utils.importlib import import_module
+
+
+class ChoiceEnum(object):
+    def __init__(self, choices):
+        if isinstance(choices, basestring):
+            choices=choices.split()
+        if isinstance(choices, (list,tuple)) and all(isinstance(x, tuple) and len(x)==2 for x in choices):
+            values=choices
+        else:
+            values=zip(itertools.count(1), choices)
+        for v, n in values:
+            name=re.sub('[- ]', '_', n.upper())
+            setattr(self, name, v)
+        self._choices=values
+
+    def __getitem__(self, idx):
+        return self._choices[idx]

emailform/views.py

+from __future__ import absolute_import
+
+import logging
+
+from django.conf import settings
+from django.core.mail import EmailMultiAlternatives
+from django.core.urlresolvers import reverse, NoReverseMatch
+from django.http import HttpResponse, HttpResponseRedirect, Http404
+from django.shortcuts import get_object_or_404, render_to_response
+from django.template import RequestContext as _rc
+from django.template.loader import render_to_string
+
+from .forms import forms_for_contact_form, BaseOptionAnswer
+from .models import ContactForm, Entry, OPTION_TYPE_CHOICES
+
+from .util import ChoiceEnum
+from . import settings as local_settings
+
+
+def _get_remote_ip(request):
+    forwarded = request.META.get('HTTP_X_FORWARDED_FOR')
+    if forwarded:
+        return forwarded.split(',')[-1].strip()
+    return request.META['REMOTE_ADDR']
+
+
+def _get_contact_form_or_404(request, slug):
+    if request.user.is_staff:
+        return get_object_or_404(ContactForm.objects, slug=slug)
+    else:
+        return get_object_or_404(ContactForm.live, slug=slug)
+
+
+def _contact_form_submit(request, contact_form, embed=False):
+    forms = forms_for_contact_form(contact_form, request)
+    if all(form.is_valid() for form in forms):
+        all_fields = []
+        for field in forms:
+            for k, v in field.clean().iteritems():
+                if isinstance(field, BaseOptionAnswer):
+                    v = ','.join(v)
+                all_fields.append('%s : %s' % (field.question.label, v))
+        entry = Entry(contact_form=contact_form,
+                      ip_address=_get_remote_ip(request),
+                      content=render_to_string(
+                        ('contact_form/%s_completed_form.html'
+                         % (contact_form.slug),
+                        'contact_form/completed_form.html'),
+                        dict(all_fields=all_fields)))
+        entry.save()
+        if contact_form.email:
+            _send_contact_form_email(request, contact_form, entry)
+        return _contact_form_show_form(request,
+                                       contact_form,
+                                       (),
+                                       entered=True,
+                                       embed=embed)
+    else:
+        return _contact_form_show_form(request,
+                                       contact_form,
+                                       forms,
+                                       embed=embed)
+
+
+def _url_for_edit(request, obj):
+    view_args = (obj._meta.app_label, obj._meta.module_name,)
+    try:
+        edit_url = reverse("admin:%s_%s_change" % view_args, args=(obj.id,))
+    except NoReverseMatch:
+        edit_url = "/admin/%s/%s/%d/" % (view_args + (obj.id,))
+    admin_url = local_settings.CONTACTFORM_ADMIN_SITE
+    if not admin_url:
+        admin_url = "http://" + request.META["HTTP_HOST"]
+    elif len(admin_url) < 4 or admin_url[:4].lower() != "http":
+        admin_url = "http://" + admin_url
+    return admin_url + edit_url
+
+
+def _send_contact_form_email(request, contact_form, entry):
+    subject = contact_form.title
+    sender = local_settings.CONTACTFORM_EMAIL_FROM
+    recipients = list(set(x.strip() for x in contact_form.email.split(',')))
+    links = [(_url_for_edit(request, entry), "Edit Submission"),
+             (_url_for_edit(request, contact_form), "Edit Contact Form"), ]
+    html_email = entry.content
+    email_msg = EmailMultiAlternatives(subject,
+                                       html_email,
+                                       sender,
+                                       recipients)
+    email_msg.attach_alternative(html_email, 'text/html')
+    try:
+        email_msg.send()
+    except smtplib.SMTPException as ex:
+        logging.exception("SMTP error sending email: %s" % str(ex))
+    except Exception as ex:
+        logging.exception("Unexpected error sending email: %s" % str(ex))
+
+
+def _contact_form_show_form(request,
+                            contact_form,
+                            forms,
+                            entered=False,
+                            embed=False):
+    if embed:
+        specific_template = ('contact_form/%s_contact_form_detail_embed.html' %
+                        contact_form.slug)
+        return render_to_response([specific_template,
+                               'contact_form/contact_form_detail_embed.html'],
+                              dict(contact_form=contact_form,
+                                   forms=forms,
+                                   entered=entered,
+                                   embed=embed),
+                              _rc(request))
+    specific_template = ('contact_form/%s_contact_form_detail.html' %
+                    contact_form.slug)
+    return render_to_response([specific_template,
+                           'contact_form/contact_form_detail.html'],
+                          dict(contact_form=contact_form,
+                               entered=entered,
+                               forms=forms,
+                               embed=embed),
+                          _rc(request))
+
+
+def contact_form_detail(request, slug, embed=False):
+    contact_form = _get_contact_form_or_404(request, slug)
+    if request.method == 'POST':
+        return _contact_form_submit(request, contact_form, embed=embed)
+    forms = forms_for_contact_form(contact_form, request)
+    return _contact_form_show_form(request, contact_form, forms, embed=embed)
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
+
+import os
+readme_file = os.path.join(os.path.dirname(__file__),
+                           'README')
+long_description = open(readme_file).read()    
+
+setup(name='django-emailcrowdsourcing',
+      version='.1',
+      description='Django app for creating multi-input forms with no default fields, with optional email notification of entries .',
+      long_description=long_description,
+      author='Glenn Mohre',
+      author_email='glenn.mohre@gmail.com',
+      url='http://www.bitbucket.org/glennmohre/django-emailform',
+      license='MIT',
+     )
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.