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.
Michael Elsdörfer / miracle2k
11 months ago

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
        return self.cleaned_data['body']
30
        for fieldname in ('name', 'email',)
31
            if self.fields[fieldname].initial:
32
                self.fields[fieldname].widget = \
33
                    DefaultValueWidget(self.fields[fieldname].initial)