miracle2k / djutils
My personal collection of small Django utilities and extensions.
Clone this repository (size: 392.9 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/miracle2k/djutils/
| commit 144: | 0b92442f9877 |
| parent 143: | 97f92c32c621 |
| branch: | djutils |
Moved the contact form to a separate project (a fork of the original django-contact-form, on which this module was based); Since the 'default_allow_change' option depends on a djutils class and thus can't be part of the django-contact-form fork, it is instead now implemented as a mixin and remains here.
Changed (Δ9.3 KB):
raw changeset »
djutils/forms/contact.py (23 lines added, 230 lines removed)
Up to file-list djutils/forms/contact.py:
1 |
"""A base contact form for allowing users to send email messages through |
|
2 |
a web interface, and a subclass demonstrating useful functionality. |
|
1 |
from djutils.forms.widgets import DefaultValueWidget |
|
3 |
2 |
|
4 |
From |
|
5 |
http://code.google.com/p/django-contact-form/ (Revsion 42) |
|
6 |
3 |
|
7 |
Modified to remove dependency on sites-framework. |
|
8 |
""" |
|
4 |
__all__ = ('ContactFormForceDefaultsMixin',) |
|
9 |
5 |
|
10 |
from django import forms |
|
11 |
from django.conf import settings |
|
12 |
from django.core.mail import send_mail |
|
13 |
from django.template import loader as django_loader, RequestContext |
|
14 |
6 |
|
7 |
class ContactFormForceDefaultsMixin(object): |
|
8 |
"""Makes the the contact form's ``name`` and ``email`` fields fixed |
|
9 |
and uneditable by the user, if they have an ``initial`` value. |
|
15 |
10 |
|
16 |
__all__ = ('ContactForm', 'AkismetContactForm',) |
|
11 |
This is originally intended as an extension to the |
|
12 |
``default_to_user`` option of ``django-contact-form``s ``ContactForm`` |
|
13 |
class, which would prefill the ``initial`` values of the fields with |
|
14 |
the current users's data. ``default_to_user`` is currently only |
|
15 |
supported by my fork of the project: |
|
17 |
16 |
|
17 |
http://bitbucket.org/miracle2k/django-contact-form/ |
|
18 |
18 |
|
19 |
# I put this on all required fields, because it's easier to pick up |
|
20 |
# on them with CSS or JavaScript if they have a class of "required" |
|
21 |
# in the HTML. Your mileage may vary. |
|
22 |
attrs_dict = { 'class': 'required' } |
|
19 |
However, this doesn't rely on any of it's funtionality per-se, and |
|
20 |
should also work with the standard version. |
|
23 |
21 |
|
22 |
We implement this as a standalone mixin rather than directly in the |
|
23 |
``ContactForm`` class, because it relies on our own |
|
24 |
``DefaultValueWidget``. |
|
25 |
""" |
|
24 |
26 |
|
25 |
class ContactForm(forms.Form): |
|
26 |
""" |
|
27 |
Base contact form class from which all contact form classes should |
|
28 |
inherit. |
|
27 |
def __init__(self, *args, **kwargs): |
|
28 |
super(ContactFormForceDefaultsMixin, self).__init__(*args, **kwargs) |
|
29 |
29 |
|
30 |
If you don't need any custom functionality, you can simply use |
|
31 |
this form to provide basic contact functionality; it will collect |
|
32 |
name, email address and message. |
|
33 |
||
34 |
The ``contact_form`` view included in this application knows how |
|
35 |
to work with this form and can handle many types of subclasses as |
|
36 |
well (see below for a discussion of the important points), so in |
|
37 |
many cases it will be all that you need. If you'd like to use this |
|
38 |
form or a subclass of it from one of your own views, just do the |
|
39 |
following: |
|
40 |
||
41 |
1. When you instantiate the form, pass the current |
|
42 |
``HttpRequest`` object to the constructor as the keyword |
|
43 |
argument ``request``; this is used internally by the base |
|
44 |
implementation, and also made available so that subclasses |
|
45 |
can add functionality which relies on inspecting the |
|
46 |
request. |
|
47 |
||
48 |
2. To send the message, call the form's ``save`` method, which |
|
49 |
accepts the keyword argument ``fail_silently`` and defaults |
|
50 |
it to ``False``. This argument is passed directly to |
|
51 |
``send_mail``, and allows you to suppress or raise |
|
52 |
exceptions as needed for debugging. The ``save`` method has |
|
53 |
no return value. |
|
54 |
||
55 |
Other than that, treat it like any other form; validity checks and |
|
56 |
validated data are handled normally, through the ``is_valid`` |
|
57 |
method and the ``cleaned_data`` dictionary. |
|
58 |
||
59 |
||
60 |
Base implementation |
|
61 |
------------------- |
|
62 |
||
63 |
Under the hood, this form uses a somewhat abstracted interface in |
|
64 |
order to make it easier to subclass and add functionality. There |
|
65 |
are several important attributes subclasses may want to look at |
|
66 |
overriding, all of which will work (in the base implementation) as |
|
67 |
either plain attributes or as callable methods: |
|
68 |
||
69 |
* ``from_email`` -- used to get the address to use in the |
|
70 |
``From:`` header of the message. The base implementation |
|
71 |
returns the value of the ``DEFAULT_FROM_EMAIL`` setting. |
|
72 |
||
73 |
* ``message`` -- used to get the message body as a string. The |
|
74 |
base implementation renders a template using the form's |
|
75 |
``cleaned_data`` dictionary as context. |
|
76 |
||
77 |
* ``recipient_list`` -- used to generate the list of |
|
78 |
recipients for the message. The base implementation returns |
|
79 |
the email addresses specified in the ``MANAGERS`` setting. |
|
80 |
||
81 |
* ``subject`` -- used to generate the subject line for the |
|
82 |
message. The base implementation returns the string 'Message |
|
83 |
sent through the web site', with the name of the current |
|
84 |
``Site`` prepended. |
|
85 |
||
86 |
* ``template_name`` -- used by the base ``message`` method to |
|
87 |
determine which template to use for rendering the |
|
88 |
message. Default is ``contact_form/contact_form.txt``. |
|
89 |
||
90 |
* ``default_to_user`` -- defaults to False; If True, and a user is |
|
91 |
currently logged in, this user's name and email are used for the |
|
92 |
sender fields per default. |
|
93 |
||
94 |
* ``default_allow_change`` -- only if ``default_to_user`` is enabled. |
|
95 |
If True, the default sender data will only be used as a suggestion |
|
96 |
via ``initial``. Otherwise it'll be fixed and cannot be changed. |
|
97 |
||
98 |
Internally, the base implementation ``_get_message_dict`` method |
|
99 |
collects ``from_email``, ``message``, ``recipient_list`` and |
|
100 |
``subject`` into a dictionary, which the ``save`` method then |
|
101 |
passes directly to ``send_mail`` as keyword arguments. |
|
102 |
||
103 |
Particularly important is the ``message`` attribute, with its base |
|
104 |
implementation as a method which renders a template; because it |
|
105 |
passes ``cleaned_data`` as the template context, any additional |
|
106 |
fields added by a subclass will automatically be available in the |
|
107 |
template. This means that many useful subclasses can get by with |
|
108 |
just adding a few fields and possibly overriding |
|
109 |
``template_name``. |
|
110 |
||
111 |
Much useful functionality can be achieved in subclasses without |
|
112 |
having to override much of the above; adding additional validation |
|
113 |
methods works the same as any other form, and typically only a few |
|
114 |
items -- ``recipient_list`` and ``subject_line``, for example, |
|
115 |
need to be overridden to achieve customized behavior. |
|
116 |
||
117 |
||
118 |
Other notes for subclassing |
|
119 |
--------------------------- |
|
120 |
||
121 |
Subclasses which want to inspect the current ``HttpRequest`` to |
|
122 |
add functionality can access it via the attribute ``request``; the |
|
123 |
base ``message`` takes advantage of this to use ``RequestContext`` |
|
124 |
when rendering its template. See the ``AkismetContactForm`` |
|
125 |
subclass in this file for an example of using the request to |
|
126 |
perform additional validation. |
|
127 |
||
128 |
Subclasses which override ``__init__`` need to accept ``*args`` |
|
129 |
and ``**kwargs``, and pass them via ``super`` in order to ensure |
|
130 |
proper behavior. |
|
131 |
||
132 |
Subclasses should be careful if overriding ``_get_message_dict``, |
|
133 |
since that method **must** return a dictionary suitable for |
|
134 |
passing directly to ``send_mail`` (unless ``save`` is overridden |
|
135 |
as well). |
|
136 |
||
137 |
Overriding ``save`` is relatively safe, though remember that code |
|
138 |
which uses your form will expect ``save`` to accept the |
|
139 |
``fail_silently`` keyword argument. In the base implementation, |
|
140 |
that argument defaults to ``False``, on the assumption that it's |
|
141 |
far better to notice errors than to silently not send mail from |
|
142 |
the contact form (see also the Zen of Python: "Errors should never |
|
143 |
pass silently, unless explicitly silenced"). |
|
144 |
||
145 |
""" |
|
146 |
def __init__(self, data=None, request=None, template_loader=None, |
|
147 |
*args, **kwargs): |
|
148 |
if request is None: |
|
149 |
raise TypeError("Keyword argument 'request' must be supplied") |
|
150 |
super(ContactForm, self).__init__(data, *args, **kwargs) |
|
151 |
self.request = request |
|
152 |
if template_loader: |
|
153 |
self.template_loader = template_loader |
|
154 |
||
155 |
# Optionally, we can use the currently logged in user as default from |
|
156 |
default_to_user = callable(self.default_to_user) and \ |
|
157 |
self.default_to_user() or self.default_to_user |
|
158 |
default_allow_change = callable(self.default_allow_change) and \ |
|
159 |
self.default_allow_change() or self.default_allow_change |
|
160 |
if default_to_user and request.user.is_authenticated(): |
|
161 |
if default_allow_change: |
|
162 |
self.fields['name'].initial = request.user.username |
|
163 |
self.fields['email'].initial = request.user.email |
|
164 |
else: |
|
165 |
from djutils.forms.widgets import DefaultValueWidget |
|
166 |
self.fields['name'].widget = DefaultValueWidget(request.user.username) |
|
167 |
self.fields['email'].widget = DefaultValueWidget(request.user.email) |
|
168 |
||
169 |
name = forms.CharField(max_length=100, |
|
170 |
widget=forms.TextInput(attrs=attrs_dict), |
|
171 |
label=u'Your name') |
|
172 |
email = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict, |
|
173 |
maxlength=200)), |
|
174 |
label=u'Your email address') |
|
175 |
body = forms.CharField(widget=forms.Textarea(attrs=attrs_dict), |
|
176 |
label=u'Your message') |
|
177 |
||
178 |
from_email = settings.DEFAULT_FROM_EMAIL |
|
179 |
||
180 |
recipient_list = [mail_tuple[1] for mail_tuple in settings.MANAGERS] |
|
181 |
||
182 |
subject = "Message sent through the web site" |
|
183 |
||
184 |
template_name = 'contact_form/contact_form.txt' |
|
185 |
template_loader = django_loader |
|
186 |
||
187 |
default_to_user = True |
|
188 |
default_allow_change = True |
|
189 |
||
190 |
def message(self): |
|
191 |
""" |
|
192 |
Renders the body of the message to a string. |
|
193 |
||
194 |
""" |
|
195 |
if callable(self.template_name): |
|
196 |
template_name = self.template_name() |
|
197 |
else: |
|
198 |
template_name = self.template_name |
|
199 |
t = self.template_loader.get_template(template_name) |
|
200 |
return t.render(RequestContext(self.request, self.cleaned_data)) |
|
201 |
||
202 |
def _get_message_dict(self): |
|
203 |
if not self.is_valid(): |
|
204 |
raise ValueError("Message cannot be sent from invalid contact form") |
|
205 |
message_dict = {} |
|
206 |
for message_part in ('from_email', 'message', 'recipient_list', 'subject'): |
|
207 |
attr = getattr(self, message_part) |
|
208 |
message_dict[message_part] = callable(attr) and attr() or attr |
|
209 |
return message_dict |
|
210 |
||
211 |
def save(self, fail_silently=False): |
|
212 |
""" |
|
213 |
Builds and sends the email message. |
|
214 |
||
215 |
""" |
|
216 |
send_mail(fail_silently=fail_silently, **self._get_message_dict()) |
|
217 |
||
218 |
||
219 |
class AkismetContactForm(ContactForm): |
|
220 |
""" |
|
221 |
Contact form which doesn't add any extra fields, but does add an |
|
222 |
Akismet spam check to the validation routine. |
|
223 |
||
224 |
Requires the setting ``AKISMET_API_KEY``, which should be a valid |
|
225 |
Akismet API key. |
|
226 |
||
227 |
""" |
|
228 |
def clean_body(self): |
|
229 |
if 'body' in self.cleaned_data and hasattr(settings, 'AKISMET_API_KEY') and settings.AKISMET_API_KEY: |
|
230 |
from akismet import Akismet |
|
231 |
akismet_api = Akismet(key=settings.AKISMET_API_KEY, |
|
232 |
blog_url='http://%s/' % Site.objects.get_current().domain) |
|
233 |
if akismet_api.verify_key(): |
|
234 |
akismet_data = { 'comment_type': 'comment', |
|
235 |
'referer': self.request.META.get('HTTP_REFERER', ''), |
|
236 |
'user_ip': self.request.META.get('REMOTE_ADDR', ''), |
|
237 |
'user_agent': self.request.META.get('HTTP_USER_AGENT', '') } |
|
238 |
if akismet_api.comment_check(self.cleaned_data['body'], data=akismet_data, build_data=True): |
|
239 |
raise forms.ValidationError(u"Akismet thinks this message is spam") |
|
240 |
|
|
30 |
for fieldname in ('name', 'email',) |
|
31 |
if self.fields[fieldname].initial: |
|
32 |
self.fields[fieldname].widget = \ |
|
33 |
DefaultValueWidget(self.fields[fieldname].initial) |
