--- /dev/null
+"""
+Generic app for creating contact forms.
+
+0. Add 'contact' to your INSTALLED_APPS and include 'contact.urls' somewhere
+in your urls.py, like:
+ url(r'^contact/',
+ include('contact.urls'))
+
+1. Migrate.
+
+2. Create somewhere in your project a module with some subclasses of
+contact.forms.ContactForm, specyfing form_tag and some fields in each.
+
+3. Set CONTACT_FORMS_MODULE in your settings to point to the module.
+
+4. Link to the form with {% url 'contact_form' form_tag %}.
+
+5. Optionally override some templates in form-specific template directories
+(/contact/<form_tag>/...).
+
+6. Receive submitted forms by email and read them in admin.
+
+
+Example:
+========
+
+settings.py:
+ CONTACT_FORMS_MODULE = 'myproject.contact_forms'
+
+myproject/contact_forms.py:
+ from django import forms
+ from contact.forms import ContactForm
+ from django.utils.translation import ugettext_lazy as _
+
+ class RegistrationForm(ContactForm):
+ form_tag = 'register'
+ name = forms.CharField(label=_('Name'), max_length=128)
+ presentation = forms.FileField(label=_('Presentation'))
+
+some_template.html:
+ {% url 'contact:form' 'register' %}
+
+"""
+
+from prawokultury.helpers import AppSettings
+
+
+class Settings(AppSettings):
+ FORMS_MODULE = "contact_forms"
+
+
+app_settings = Settings('CONTACT')
--- /dev/null
+from django.contrib import admin
+from django.forms import ModelForm
+from .models import Contact
+from django.utils.translation import ugettext as _
+from .forms import contact_forms
+from django.template import Template
+from django.utils.safestring import mark_safe
+
+
+class ContactAdmin(admin.ModelAdmin):
+ date_hierarchy = 'created_at'
+ list_display = ('created_at', 'contact', 'form_tag')
+ fields = ['form_tag', 'created_at', 'contact', 'ip']
+ readonly_fields = ['form_tag', 'created_at', 'contact', 'ip']
+
+ def change_view(self, request, object_id, extra_context=None):
+ if object_id:
+ try:
+ instance = Contact.objects.get(pk=object_id)
+ assert isinstance(instance.body, dict)
+ except (Contact.DoesNotExist, AssertionError):
+ pass
+ else:
+ # Create readonly fields from the body JSON.
+ body_fields = ['body__%s' % k for k in instance.body.keys()]
+ attachments = list(instance.attachment_set.all())
+ body_fields += ['body__%s' % a.tag for a in attachments]
+ self.readonly_fields.extend(body_fields)
+
+ # Find the original form.
+ try:
+ orig_fields = contact_forms[instance.form_tag]().fields
+ except KeyError:
+ orig_fields = {}
+
+ # Try to preserve the original order.
+ admin_fields = []
+ orig_keys = list(orig_fields.keys())
+ while orig_keys:
+ key = orig_keys.pop(0)
+ key = "body__%s" % key
+ if key in body_fields:
+ admin_fields.append(key)
+ body_fields.remove(key)
+ admin_fields.extend(body_fields)
+
+ self.fieldsets = [
+ (None, {'fields': self.fields}),
+ (_('Body'), {'fields': admin_fields}),
+ ]
+
+ # Create field getters for fields and attachments.
+ for k, v in instance.body.items():
+ f = (lambda v: lambda self: v)(v)
+ f.short_description = orig_fields[k].label if k in orig_fields else _(k)
+ setattr(self, "body__%s" % k, f)
+
+ download_link = "<a href='%(url)s'>%(url)s</a>"
+ for attachment in attachments:
+ k = attachment.tag
+ link = mark_safe(download_link % {
+ 'url': attachment.get_absolute_url()})
+ f = (lambda v: lambda self: v)(link)
+ f.short_description = orig_fields[k].label if k in orig_fields else _(k)
+ setattr(self, "body__%s" % k, f)
+ return super(ContactAdmin, self).change_view(request, object_id,
+ extra_context=extra_context)
+
+
+admin.site.register(Contact, ContactAdmin)
--- /dev/null
+from django.contrib.sites.models import Site
+from django.core.files.uploadedfile import UploadedFile
+from django.core.mail import send_mail, mail_managers
+from django.core.validators import email_re
+from django import forms
+from django.template.loader import render_to_string
+from django.template import RequestContext
+from .models import Attachment, Contact
+
+
+contact_forms = {}
+class ContactFormMeta(forms.Form.__metaclass__):
+ def __new__(cls, *args, **kwargs):
+ model = super(ContactFormMeta, cls).__new__(cls, *args, **kwargs)
+ assert model.form_tag not in contact_forms, 'Duplicate form_tag.'
+ contact_forms[model.form_tag] = model
+ return model
+
+
+class ContactForm(forms.Form):
+ """Subclass and define some fields."""
+ __metaclass__ = ContactFormMeta
+ required_css_class = 'required'
+ contact = forms.CharField(max_length=128)
+ form_tag = None
+
+ def save(self, request):
+ body = {}
+ for name, value in self.cleaned_data.items():
+ if not isinstance(value, UploadedFile) and name != 'contact':
+ body[name] = value
+ contact = Contact.objects.create(body=body,
+ ip=request.META['REMOTE_ADDR'],
+ contact=self.cleaned_data['contact'],
+ form_tag=self.form_tag)
+ for name, value in self.cleaned_data.items():
+ if isinstance(value, UploadedFile):
+ attachment = Attachment(contact=contact, tag=name)
+ attachment.file.save(value.name, value)
+ attachment.save()
+
+ dictionary = {
+ 'form_tag': self.form_tag,
+ 'site': Site.objects.get_current(),
+ 'contact': contact,
+ 'admin_url': '',
+ }
+ context = RequestContext(request)
+ mail_managers_subject = render_to_string([
+ 'contact/%s/mail_managers_subject.txt' % self.form_tag,
+ 'contact/mail_managers_subject.txt',
+ ], dictionary, context)
+ mail_managers_body = render_to_string([
+ 'contact/%s/mail_managers_body.txt' % self.form_tag,
+ 'contact/mail_managers_body.txt',
+ ], dictionary, context)
+ mail_managers(mail_managers_subject, mail_managers_body,
+ fail_silently=True)
+
+ if email_re.match(contact.contact):
+ mail_subject = render_to_string([
+ 'contact/%s/mail_subject.txt' % self.form_tag,
+ 'contact/mail_subject.txt',
+ ], dictionary, context)
+ mail_body = render_to_string([
+ 'contact/%s/mail_body.txt' % self.form_tag,
+ 'contact/mail_body.txt',
+ ], dictionary, context)
+ send_mail(mail_subject, mail_body,
+ 'no-reply@%s' % dictionary['site'].domain,
+ [contact.contact],
+ fail_silently=True)
+
+ return contact
--- /dev/null
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2012-10-08 16:48+0200\n"
+"PO-Revision-Date: 2012-10-08 16:51+0100\n"
+"Last-Translator: Radek Czajka <radoslaw.czajka@nowoczesnapolska.org.pl>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+
+#: admin.py:49
+msgid "Body"
+msgstr "Treść"
+
+#: models.py:10
+msgid "submission date"
+msgstr "data wysłania"
+
+#: models.py:11
+msgid "IP address"
+msgstr "adres IP"
+
+#: models.py:12
+msgid "contact"
+msgstr "kontakt"
+
+#: models.py:13
+msgid "form"
+msgstr "formularz"
+
+#: models.py:14
+msgid "body"
+msgstr "treść"
+
+#: models.py:18
+msgid "submitted form"
+msgstr "przysłany formularz"
+
+#: models.py:19
+msgid "submitted forms"
+msgstr "przysłane formularze"
+
+#: templates/contact/form.html:5
+msgid "Contact form"
+msgstr "Formularz kontaktowy"
+
+#: templates/contact/form.html:14
+msgid "Submit"
+msgstr "Wyślij"
+
+#: templates/contact/mail_body.txt:2
+#: templates/contact/mail_subject.txt:1
+#, python-format
+msgid "Thank you for contacting us at %(site.name)s."
+msgstr "Dziękujemy za skontaktowanie się z nami na stronie %(site.name)s."
+
+#: templates/contact/mail_body.txt:3
+msgid "Your submission has been referred to the project coordinator."
+msgstr "Twoje zgłoszenie zostało przekazane osobie koordynującej projekt."
+
+#: templates/contact/mail_body.txt:6
+msgid "Message sent automatically. Please do not reply to it."
+msgstr "Wiadomość wysłana automatycznie, prosimy nie odpowiadać."
+
+#: templates/contact/thanks.html:5
+msgid "Thank you"
+msgstr "Dziękujemy"
+
+#: templates/contact/thanks.html:8
+msgid "Thank you for submitting the contact form."
+msgstr "Dziękujemy za wypełnienie formularza kontaktowego."
+
--- /dev/null
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'Contact'
+ db.create_table('contact_contact', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('created_at', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('ip', self.gf('django.db.models.fields.IPAddressField')(max_length=15)),
+ ('contact', self.gf('django.db.models.fields.CharField')(max_length=128)),
+ ('form_tag', self.gf('django.db.models.fields.CharField')(max_length=32)),
+ ('body', self.gf('jsonfield.fields.JSONField')()),
+ ))
+ db.send_create_signal('contact', ['Contact'])
+
+
+ def backwards(self, orm):
+ # Deleting model 'Contact'
+ db.delete_table('contact_contact')
+
+
+ models = {
+ 'contact.contact': {
+ 'Meta': {'ordering': "('-created_at',)", 'object_name': 'Contact'},
+ 'body': ('jsonfield.fields.JSONField', [], {}),
+ 'contact': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'form_tag': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'})
+ }
+ }
+
+ complete_apps = ['contact']
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ # Adding model 'Attachment'
+ db.create_table('contact_attachment', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('contact', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contact.Contact'])),
+ ('tag', self.gf('django.db.models.fields.CharField')(max_length=64)),
+ ('file', self.gf('django.db.models.fields.files.FileField')(max_length=100)),
+ ))
+ db.send_create_signal('contact', ['Attachment'])
+
+ # Adding index on 'Contact', fields ['form_tag']
+ db.create_index('contact_contact', ['form_tag'])
+
+
+ def backwards(self, orm):
+ # Removing index on 'Contact', fields ['form_tag']
+ db.delete_index('contact_contact', ['form_tag'])
+
+ # Deleting model 'Attachment'
+ db.delete_table('contact_attachment')
+
+
+ models = {
+ 'contact.attachment': {
+ 'Meta': {'object_name': 'Attachment'},
+ 'contact': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contact.Contact']"}),
+ 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'tag': ('django.db.models.fields.CharField', [], {'max_length': '64'})
+ },
+ 'contact.contact': {
+ 'Meta': {'ordering': "('-created_at',)", 'object_name': 'Contact'},
+ 'body': ('jsonfield.fields.JSONField', [], {}),
+ 'contact': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'form_tag': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'})
+ }
+ }
+
+ complete_apps = ['contact']
\ No newline at end of file
--- /dev/null
+# -*- coding: utf-8 -*-
+from django.core.files.storage import FileSystemStorage
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from jsonfield import JSONField
+from . import app_settings
+
+
+class Contact(models.Model):
+ created_at = models.DateTimeField(_('submission date'), auto_now_add=True)
+ ip = models.IPAddressField(_('IP address'))
+ contact = models.CharField(_('contact'), max_length=128)
+ form_tag = models.CharField(_('form'), max_length=32, db_index=True)
+ body = JSONField(_('body'))
+
+ class Meta:
+ ordering = ('-created_at',)
+ verbose_name = _('submitted form')
+ verbose_name_plural = _('submitted forms')
+
+ def __unicode__(self):
+ return unicode(self.created_at)
+
+
+class Attachment(models.Model):
+ contact = models.ForeignKey(Contact)
+ tag = models.CharField(max_length=64)
+ file = models.FileField(upload_to='contact/attachment')
+
+ @models.permalink
+ def get_absolute_url(self):
+ return ('contact_attachment', [self.contact_id, self.tag])
+
+
+__import__(app_settings.FORMS_MODULE)
--- /dev/null
+{% extends "base.html" %}
+{% load i18n %}
+{% block "body" %}
+
+ <h1>{% block contact_form_title %}{% trans "Contact form" %}{% endblock %}</h1>
+
+ {% block contact_form_description %}
+ {% endblock %}
+
+ <form method="POST" action="." enctype="multipart/form-data">
+ {% csrf_token %}
+ <table>
+ {{ form.as_table }}
+ <tr><td></td><td><button>{% block contact_form_submit %}{% trans "Submit" %}{% endblock %}</button></td></tr>
+ </table>
+ </form>
+
+{% endblock %}
\ No newline at end of file
--- /dev/null
+{% load i18n %}
+{% blocktrans %}Thank you for contacting us at {{ site.name }}.{% endblocktrans %}
+{% trans "Your submission has been referred to the project coordinator." %}
+
+--
+{% trans "Message sent automatically. Please do not reply to it." %}
--- /dev/null
+Wypełniono formularz {{ form_tag }} na stronie {{ site.name }}.
+
+http://{{ site.domain }}{{ admin_url }}
+
+{{ contact.body }}
--- /dev/null
+Wypełniono formularz {{ form_tag }} na stronie {{ site.name }}.
\ No newline at end of file
--- /dev/null
+{% load i18n %}{% blocktrans %}Thank you for contacting us at {{ site.name }}.{% endblocktrans %}
\ No newline at end of file
--- /dev/null
+{% extends "base.html" %}
+{% load i18n %}
+{% block "body" %}
+
+ <h1>{% block contact_form_title %}{% trans "Thank you" %}{% endblock %}</h1>
+
+ {% block contact_form_description %}
+ <p class="notice">{% trans "Thank you for submitting the contact form." %}</p>
+ {% endblock %}
+
+{% endblock %}
\ No newline at end of file
--- /dev/null
+from django.conf.urls.defaults import *
+from . import views
+
+urlpatterns = patterns('contact.views',
+ url(r'^(?P<form_tag>[^/]+)/$', views.form, name='contact_form'),
+ url(r'^(?P<form_tag>[^/]+)/thanks/$', views.thanks, name='contact_thanks'),
+ url(r'^attachment/(?P<contact_id>\d+)/(?P<tag>[^/]+)/$',
+ views.attachment, name='contact_attachment'),
+)
--- /dev/null
+from django.contrib.auth.decorators import permission_required
+from django.http import Http404, HttpResponse
+from django.shortcuts import get_object_or_404, redirect, render
+from django.utils.translation import ugettext_lazy as _
+from prawokultury.helpers import serve_file
+from .forms import contact_forms
+from .models import Attachment
+
+
+def form(request, form_tag):
+ try:
+ form_class = contact_forms[form_tag]
+ except KeyError:
+ raise Http404
+ if request.method == 'POST':
+ form = form_class(request.POST, request.FILES)
+ if form.is_valid():
+ form.save(request)
+ return redirect('contact_thanks', form_tag)
+ else:
+ form = form_class()
+ return render(request,
+ ['contact/%s/form.html' % form_tag, 'contact/form.html'],
+ {'form': form}
+ )
+
+
+def thanks(request, form_tag):
+ if form_tag not in contact_forms:
+ raise Http404
+
+ return render(request,
+ ['contact/%s/thanks.html' % form_tag, 'contact/thanks.html']
+ )
+
+
+@permission_required('contact.change_attachment')
+def attachment(request, contact_id, tag):
+ attachment = get_object_or_404(Attachment, contact_id=contact_id, tag=tag)
+ return serve_file(attachment.file.url)
--- /dev/null
+from django import forms
+from contact.forms import ContactForm
+from django.utils.translation import ugettext_lazy as _
+
+
+class RegistrationForm(ContactForm):
+ form_tag = 'register'
+ name = forms.CharField(label=_('Name'), max_length=128)
+ organization = forms.CharField(label=_('Organization'), max_length=128)
+ summary = forms.CharField(label=_('Summary'), widget=forms.Textarea)
+ presentation = forms.FileField(label=_('Presentation'))
# Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information.
#
from django.conf import settings
+from django.http import HttpResponse, HttpResponseRedirect
from textile import Textile
if hasattr(self, more):
value = getattr(self, more)(value)
return value
+
+
+def serve_file(url):
+ if settings.X_ACCEL_REDIRECT:
+ response = HttpResponse()
+ response['Content-Type'] = ""
+ response['X-Accel-Redirect'] = url
+ return response
+ else:
+ return HttpResponseRedirect(url)
'prawokultury',
#'events',
'migdal',
- #'forms',
+ 'contact',
'gravatar',
'south',
'pagination',
'sorl.thumbnail',
'piwik.django',
+ 'django_cas',
'django.contrib.auth',
'django.contrib.contenttypes',
EntryType('info', gettext('info'), commentable=False, on_main=False),
)
-MIGDAL_TAXONOMIES = ()
\ No newline at end of file
+MIGDAL_TAXONOMIES = ()
+
+CONTACT_FORMS_MODULE = 'prawokultury.contact_forms'
+
+# Use Nginx's X-accel when serving files with helpers.serve_file().
+# See http://wiki.nginx.org/X-accel
+X_ACCEL_REDIRECT = False
--- /dev/null
+{% extends "contact/form.html" %}
+{% load i18n %}
+
+{% block contact_form_title %}{% trans "Registration form" %}{% endblock %}
+
+{% block contact_form_description %}
+{% endblock %}
) + i18n_patterns('',
url(string_concat(r'^', _('events'), r'/'), include('events.urls')),
url(r'^comments/', include('django_comments_xtd.urls')),
+ url(r'^contact/', include('contact.urls')),
) + migdal_urlpatterns
if settings.DEBUG: