├── .coveragerc ├── .gitignore ├── .travis.yml ├── APACHE_LICENSE ├── AUTHORS.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── doc_requirements.txt ├── docs ├── Makefile ├── changes.rst ├── conf.py ├── configuration.rst ├── cookbook.rst ├── customization.rst ├── development.rst ├── index.rst ├── install.rst ├── make.bat ├── reference.rst └── usage.rst ├── envelope ├── __init__.py ├── forms.py ├── locale │ ├── el │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── es │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── fi │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── lv │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── nl │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pl │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ ├── pt_BR │ │ └── LC_MESSAGES │ │ │ ├── django.mo │ │ │ └── django.po │ └── ru │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── models.py ├── settings.py ├── signals.py ├── spam_filters.py ├── templates │ └── envelope │ │ ├── contact_form.html │ │ ├── email_body.html │ │ └── email_body.txt ├── templatetags │ ├── __init__.py │ └── envelope_tags.py ├── urls.py └── views.py ├── example_project ├── __init__.py ├── manage.py ├── requirements.txt ├── settings.py ├── templates │ ├── base.html │ └── envelope │ │ ├── contact.html │ │ ├── crispy_contact.html │ │ └── messages_contact.html └── urls.py ├── requirements.txt ├── runtests.py ├── setup.cfg ├── setup.py ├── test_requirements.txt ├── tests ├── __init__.py ├── settings.py ├── templates │ ├── customized_contact.html │ ├── envelope │ │ └── contact.html │ └── layout.html ├── test_forms.py ├── test_spam_filters.py ├── test_templatetags.py ├── test_views.py └── urls.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | omit = 3 | envelope/__init__.py 4 | .tox/* 5 | ../src/* 6 | ../lib/* 7 | */test* 8 | */lib/python* 9 | /home/travis/virtualenv/* 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.pyo 3 | *.pyc 4 | .project 5 | .pydevproject 6 | .idea/ 7 | .settings 8 | dist/ 9 | build/ 10 | *.egg-info 11 | docs/_* 12 | .coverage 13 | *,cover 14 | htmlcov 15 | .tox/ 16 | 17 | example_project/example.db 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | matrix: 4 | fast_finish: true 5 | allow_failures: 6 | - env: DJANGO=master 7 | include: 8 | - python: 2.7 9 | env: DJANGO=1.11 10 | 11 | - python: 3.5 12 | env: DJANGO=1.11 13 | - python: 3.5 14 | env: DJANGO=2.2 15 | 16 | - python: 3.6 17 | env: DJANGO=1.11 18 | - python: 3.6 19 | env: DJANGO=2.2 20 | - python: 3.6 21 | env: DJANGO=3.0 22 | - python: 3.6 23 | env: DJANGO=master 24 | 25 | - python: 3.7 26 | env: DJANGO=2.2 27 | - python: 3.7 28 | env: DJANGO=3.0 29 | - python: 3.7 30 | env: DJANGO=master 31 | 32 | 33 | - python: 3.8 34 | env: DJANGO=2.2 35 | - python: 3.8 36 | env: DJANGO=3.0 37 | - python: 3.8 38 | env: DJANGO=master 39 | 40 | install: 41 | - pip install tox tox-travis 42 | script: 43 | - tox 44 | after_success: 45 | - pip install coveralls 46 | - coveralls 47 | -------------------------------------------------------------------------------- /APACHE_LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2013 Techdrop Labs Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | AUTHORS 2 | ======= 3 | 4 | Project created by 5 | ------------------ 6 | 7 | * Zbigniew Siciarz (zbigniew at siciarz dot net) 8 | 9 | Project maintained by 10 | --------------------- 11 | 12 | * SebCorbin 13 | 14 | Contributors 15 | ------------ 16 | 17 | * aleprovencio 18 | * Antti Kaihola 19 | * Giorgos Logiotatidis 20 | * Mark Lavin 21 | * Tomasz Wysocki 22 | * Erik Simmler 23 | * Vitaly Babiy 24 | * Jacek Tomaszewski 25 | * Anders Petersson 26 | * Quentin Caron 27 | * Kristaps K 28 | * Javi Palanca 29 | * George Tantiras 30 | * Richard Barran 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2017 Zbigniew Siciarz 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include APACHE_LICENSE 2 | include LICENSE 3 | include README.rst 4 | include MANIFEST.in 5 | recursive-include envelope/templates * 6 | recursive-include envelope/tests/templates * 7 | recursive-include envelope/locale * 8 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | django-envelope 3 | =============== 4 | 5 | .. image:: https://img.shields.io/pypi/v/django-envelope.svg 6 | :target: https://pypi.python.org/pypi/django-envelope/ 7 | :alt: Latest PyPI version 8 | 9 | .. image:: https://img.shields.io/pypi/dm/django-envelope.svg 10 | :target: https://pypi.python.org/pypi/django-envelope/ 11 | :alt: Number of PyPI downloads 12 | 13 | .. image:: https://img.shields.io/pypi/pyversions/django-envelope.svg 14 | :target: https://pypi.python.org/pypi/django-envelope/ 15 | :alt: Supported Python versions 16 | 17 | .. image:: https://img.shields.io/pypi/wheel/django-envelope.svg 18 | :target: https://pypi.python.org/pypi/django-envelope/ 19 | :alt: Wheel Status 20 | 21 | .. image:: https://travis-ci.org/zsiciarz/django-envelope.png?branch=develop 22 | :target: https://travis-ci.org/zsiciarz/django-envelope 23 | 24 | .. image:: https://coveralls.io/repos/zsiciarz/django-envelope/badge.png 25 | :target: https://coveralls.io/r/zsiciarz/django-envelope 26 | 27 | 28 | ``django-envelope`` is a simple contact form app for Django web framework. 29 | 30 | Basic usage 31 | ----------- 32 | 33 | 1. Install with ``pip install django-envelope``. 34 | 2. Add ``envelope`` to your ``INSTALLED_APPS``. 35 | 3. Create a template ``envelope/contact.html`` that contains somewhere 36 | a call to ``{% render_contact_form %}`` template tag. This tag can be 37 | imported by placing ``{% load envelope_tags %}`` at the top of your 38 | template. 39 | 4. Hook the app's URLconf in your ``urls.py`` like this:: 40 | 41 | urlpatterns = patterns('', 42 | #... 43 | (r'^contact/', include('envelope.urls')), 44 | #... 45 | ) 46 | 47 | See the `docs `_ for more customization 48 | options. 49 | 50 | Resources 51 | --------- 52 | 53 | * `Documentation `_ 54 | * `Issue tracker `_ 55 | * `CI server `_ 56 | 57 | Authors 58 | ------- 59 | 60 | django-envelope was created by `Zbigniew Siciarz `_ and is 61 | now maintained by `SebCorbin `_. 62 | See AUTHORS.rst for a full list of contributors. 63 | 64 | License 65 | ------- 66 | 67 | This work is released under the MIT license. A copy of the license is provided 68 | in the LICENSE file. 69 | 70 | The HTML template comes from 71 | `Open Source Template Project `_ by 72 | sendwithus.com, distributed under the Apache 2.0 license (see the APACHE_LICENSE 73 | file for the full text). 74 | -------------------------------------------------------------------------------- /doc_requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | Jinja2==2.9.5 3 | Pygments==2.2.0 4 | Sphinx==1.5.2 5 | docutils==0.13.1 6 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-envelope.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-envelope.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-envelope" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-envelope" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | 1.4.0 6 | ----- 7 | 8 | - fixed french translation 9 | - Python 3.7, 3.8 and Django 2.2, 3.0 compatibility 10 | 11 | 1.3.0 12 | ----- 13 | 14 | - added Greek translation, thanks raratiru! 15 | - Python 3.6 and Django 1.11 compatibility 16 | 17 | 1.2.0 18 | ----- 19 | 20 | - added Latvian and Russian translations, thanks wildd! 21 | - added Spanish translations, thanks javipalanca! 22 | 23 | 1.1.0 24 | ----- 25 | 26 | - added Brazilian Portuguese translation, thanks aleprovencio! 27 | - Python 3.5 and Django 1.9 compatibility 28 | 29 | 30 | 1.0.0 31 | ----- 32 | 33 | Improvements and fixes: 34 | - HTML email support 35 | - subject field is optional by default 36 | - support for `custom User model`_ 37 | - docs: added :doc:`cookbook` 38 | 39 | Backwards incompatible changes: 40 | - removed category field from :class:`~envelope.forms.ContactForm` 41 | - ``BaseContactForm`` no longer exists; to customize form processing, subclass 42 | :class:`~envelope.forms.ContactForm` directly 43 | - :class:`~envelope.views.ContactView` does not create any flash messages; 44 | use `FormMessagesMixin`_ from `django-braces`_ (see the :doc:`cookbook` 45 | for an example) 46 | - dropped Django 1.4 compatibility 47 | - dropped Python 2.6 compatibility; use 2.7 or 3.3+ 48 | - message rejection reason from signal handlers isn't sent to the user in 49 | HTTP 400 response's body 50 | - the default ``envelope/contact.html`` template is removed; one must create 51 | the template explicitly 52 | 53 | .. _`custom User model`: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#substituting-a-custom-user-model 54 | .. _`FormMessagesMixin`: http://django-braces.readthedocs.org/en/latest/form.html#formmessagesmixin 55 | .. _`django-braces`: https://github.com/brack3t/django-braces 56 | 57 | 0.7.0 58 | ----- 59 | - added :func:`{% render_contact_form %} ` 60 | template tag 61 | - Django 1.6 compatibility 62 | - settled on 3.3 as the minimum supported Python 3 version 63 | - moved to Travis CI as the continuous integration solution 64 | 65 | 0.6.1 66 | ----- 67 | - fixed ``NameError`` in example project 68 | 69 | 0.6.0 70 | ----- 71 | - Python 3 compatibility! 72 | 73 | 0.5.1 74 | ----- 75 | - fixed template loading in tests 76 | 77 | 0.5.0 78 | ----- 79 | - contact form class is more customizable 80 | - the ``Reply-To`` header in the message is set to whatever the submitted 81 | email was 82 | - added ``after_send`` signal 83 | - `django-honeypot`_ is now just an optional dependency 84 | - ``example_project`` is no longer incorrectly distributed with the application 85 | 86 | .. _`django-honeypot`: https://github.com/sunlightlabs/django-honeypot 87 | 88 | 0.4.1 89 | ----- 90 | - security bugfix regarding initial form values 91 | 92 | 0.4.0 93 | ----- 94 | - removed the function-based view 95 | - removed ``ContactForm.send()`` method 96 | - application signals (``before_send``) 97 | - updated documentation 98 | - reworked settings 99 | - Continous Integration server, thanks to ShiningPanda 100 | 101 | 0.3.2 102 | ----- 103 | - omit the brackets if the user doesn't have a full name 104 | - honeypot is mentioned in the usage docs 105 | 106 | 0.3.1 107 | ----- 108 | - configurable recipients 109 | - better logging hierarchy 110 | - the code is more PEP-8 compliant 111 | 112 | 0.3.0 113 | ----- 114 | - introduced a class-based :class:`envelope.views.ContactView` (requires 115 | Django >= 1.3) 116 | - deprecated the function-based view ``envelope.views.contact`` 117 | - improved test coverage 118 | - more and better documentation (also hosted on Read The Docs) 119 | 120 | 0.2.1 121 | ----- 122 | - French translation added 123 | 124 | 0.2.0 125 | ----- 126 | - deprecated the ``ContactForm.send()`` method, use 127 | :meth:`envelope.forms.ContactForm.save` instead for more consistency 128 | with Django coding style 129 | - localization support 130 | 131 | 0.1.4 132 | ----- 133 | - added a more descriptive README file 134 | 135 | 0.1.3 136 | ----- 137 | - added the ``redirect_to`` optional argument to view function 138 | 139 | 0.1.2 140 | ----- 141 | - added the ``extra_context`` argument to view function 142 | 143 | 0.1.1 144 | ----- 145 | - improved setup script, added dependencies 146 | 147 | 0.1.0 148 | ----- 149 | - initial version 150 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-envelope documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Jun 1 18:45:24 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | import pkg_resources 16 | 17 | try: 18 | release = pkg_resources.get_distribution('django-envelope').version 19 | except pkg_resources.DistributionNotFound: 20 | sys.exit(1) 21 | del pkg_resources 22 | 23 | version = '.'.join(release.split('.')[:2]) 24 | 25 | # If extensions (or modules to document with autodoc) are in another directory, 26 | # add these directories to sys.path here. If the directory is relative to the 27 | # documentation root, use os.path.abspath to make it absolute, like shown here. 28 | sys.path.insert(0, os.path.abspath('..')) 29 | os.environ['DJANGO_SETTINGS_MODULE'] = 'example_project.settings' 30 | 31 | # -- General configuration ----------------------------------------------------- 32 | 33 | # If your documentation needs a minimal Sphinx version, state it here. 34 | #needs_sphinx = '1.0' 35 | 36 | # Add any Sphinx extension module names here, as strings. They can be extensions 37 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 38 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix of source filenames. 44 | source_suffix = '.rst' 45 | 46 | # The encoding of source files. 47 | #source_encoding = 'utf-8-sig' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = u'django-envelope' 54 | copyright = u'2011-2017, Zbigniew Siciarz' 55 | 56 | intersphinx_mapping = { 57 | 'django': ('https://docs.djangoproject.com/en/dev/', 'http://docs.djangoproject.com/en/dev/_objects/') 58 | } 59 | 60 | RTD_NEW_THEME = True 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | #language = None 65 | 66 | # There are two options for replacing |today|: either, you set today to some 67 | # non-false value, then it is used: 68 | #today = '' 69 | # Else, today_fmt is used as the format for a strftime call. 70 | #today_fmt = '%B %d, %Y' 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | exclude_patterns = ['_build'] 75 | 76 | # The reST default role (used for this markup: `text`) to use for all documents. 77 | #default_role = None 78 | 79 | # If true, '()' will be appended to :func: etc. cross-reference text. 80 | #add_function_parentheses = True 81 | 82 | # If true, the current module name will be prepended to all description 83 | # unit titles (such as .. function::). 84 | #add_module_names = True 85 | 86 | # If true, sectionauthor and moduleauthor directives will be shown in the 87 | # output. They are ignored by default. 88 | #show_authors = False 89 | 90 | # The name of the Pygments (syntax highlighting) style to use. 91 | pygments_style = 'sphinx' 92 | 93 | # A list of ignored prefixes for module index sorting. 94 | #modindex_common_prefix = [] 95 | 96 | 97 | # -- Options for HTML output --------------------------------------------------- 98 | 99 | # The theme to use for HTML and HTML Help pages. See the documentation for 100 | # a list of builtin themes. 101 | html_theme = 'alabaster' 102 | 103 | # Theme options are theme-specific and customize the look and feel of a theme 104 | # further. For a list of options available for each theme, see the 105 | # documentation. 106 | #html_theme_options = {} 107 | 108 | # Add any paths that contain custom themes here, relative to this directory. 109 | #html_theme_path = [] 110 | 111 | # The name for this set of Sphinx documents. If None, it defaults to 112 | # " v documentation". 113 | #html_title = None 114 | 115 | # A shorter title for the navigation bar. Default is the same as html_title. 116 | #html_short_title = None 117 | 118 | # The name of an image file (relative to this directory) to place at the top 119 | # of the sidebar. 120 | #html_logo = None 121 | 122 | # The name of an image file (within the static path) to use as favicon of the 123 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 124 | # pixels large. 125 | #html_favicon = None 126 | 127 | # Add any paths that contain custom static files (such as style sheets) here, 128 | # relative to this directory. They are copied after the builtin static files, 129 | # so a file named "default.css" will overwrite the builtin "default.css". 130 | html_static_path = ['_static'] 131 | 132 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 133 | # using the given strftime format. 134 | #html_last_updated_fmt = '%b %d, %Y' 135 | 136 | # If true, SmartyPants will be used to convert quotes and dashes to 137 | # typographically correct entities. 138 | #html_use_smartypants = True 139 | 140 | # Custom sidebar templates, maps document names to template names. 141 | #html_sidebars = {} 142 | 143 | # Additional templates that should be rendered to pages, maps page names to 144 | # template names. 145 | #html_additional_pages = {} 146 | 147 | # If false, no module index is generated. 148 | #html_domain_indices = True 149 | 150 | # If false, no index is generated. 151 | #html_use_index = True 152 | 153 | # If true, the index is split into individual pages for each letter. 154 | #html_split_index = False 155 | 156 | # If true, links to the reST sources are added to the pages. 157 | #html_show_sourcelink = True 158 | 159 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 160 | #html_show_sphinx = True 161 | 162 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 163 | #html_show_copyright = True 164 | 165 | # If true, an OpenSearch description file will be output, and all pages will 166 | # contain a tag referring to it. The value of this option must be the 167 | # base URL from which the finished HTML is served. 168 | #html_use_opensearch = '' 169 | 170 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 171 | #html_file_suffix = None 172 | 173 | # Output file base name for HTML help builder. 174 | htmlhelp_basename = 'django-envelopedoc' 175 | 176 | 177 | # -- Options for LaTeX output -------------------------------------------------- 178 | 179 | # The paper size ('letter' or 'a4'). 180 | #latex_paper_size = 'letter' 181 | 182 | # The font size ('10pt', '11pt' or '12pt'). 183 | #latex_font_size = '10pt' 184 | 185 | # Grouping the document tree into LaTeX files. List of tuples 186 | # (source start file, target name, title, author, documentclass [howto/manual]). 187 | latex_documents = [ 188 | ('index', 'django-envelope.tex', u'django-envelope Documentation', 189 | u'Zbigniew Siciarz', 'manual'), 190 | ] 191 | 192 | # The name of an image file (relative to this directory) to place at the top of 193 | # the title page. 194 | #latex_logo = None 195 | 196 | # For "manual" documents, if this is true, then toplevel headings are parts, 197 | # not chapters. 198 | #latex_use_parts = False 199 | 200 | # If true, show page references after internal links. 201 | #latex_show_pagerefs = False 202 | 203 | # If true, show URL addresses after external links. 204 | #latex_show_urls = False 205 | 206 | # Additional stuff for the LaTeX preamble. 207 | #latex_preamble = '' 208 | 209 | # Documents to append as an appendix to all manuals. 210 | #latex_appendices = [] 211 | 212 | # If false, no module index is generated. 213 | #latex_domain_indices = True 214 | 215 | 216 | # -- Options for manual page output -------------------------------------------- 217 | 218 | # One entry per manual page. List of tuples 219 | # (source start file, name, description, authors, manual section). 220 | man_pages = [ 221 | ('index', 'django-envelope', u'django-envelope Documentation', 222 | [u'Zbigniew Siciarz'], 1) 223 | ] 224 | -------------------------------------------------------------------------------- /docs/configuration.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Configuration 3 | ============= 4 | 5 | These values defined in ``settings.py`` affect the application: 6 | 7 | * ``DEFAULT_FROM_EMAIL``: This is the sender of the email sent with your 8 | contact form. 9 | 10 | .. note:: 11 | (Some mail servers do not allow sending messages from an 12 | address that is different than the one used for SMTP authentication.) 13 | 14 | * ``ENVELOPE_EMAIL_RECIPIENTS``: A list of e-mail addresses of people who will 15 | receive the message. For backwards compatibility reasons, the default value 16 | is a list where the only element is ``DEFAULT_FROM_EMAIL``. 17 | 18 | * ``ENVELOPE_SUBJECT_INTRO``: The prefix for subject line of the email message. 19 | This is different than ``EMAIL_SUBJECT_PREFIX`` which is global for the whole 20 | project. ``ENVELOPE_SUBJECT_INTRO`` goes after the global prefix and is 21 | followed by the actual subject entered in the form by website's user. 22 | 23 | Default value: *Message from contact form:* 24 | 25 | * ``ENVELOPE_USE_HTML_EMAIL``: Whether to send an HTML email along with the 26 | plaintext one. Defaults to ``True``. 27 | -------------------------------------------------------------------------------- /docs/cookbook.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Cookbook 3 | ======== 4 | 5 | Success and error messages 6 | ========================== 7 | 8 | Starting from release 1.0, :class:`envelope.views.ContactView` does not set any 9 | `messages`_ since these were customized by most users anyway. We encourage 10 | you to use the excellent `django-braces`_ app which provides a 11 | `FormMessagesMixin`_ designed specifically for this purpose. 12 | 13 | .. _`messages`: https://docs.djangoproject.com/en/dev/ref/contrib/messages/ 14 | .. _`django-braces`: https://github.com/brack3t/django-braces 15 | .. _`FormMessagesMixin`: http://django-braces.readthedocs.org/en/latest/form.html#formmessagesmixin 16 | 17 | The following example shows how to add the mixin to ``ContactView``:: 18 | 19 | from braces.views import FormMessagesMixin 20 | from envelope.views import ContactView 21 | 22 | from django.utils.translation import ugettext_lazy as _ 23 | 24 | 25 | class MyContactView(FormMessagesMixin, ContactView): 26 | form_valid_message = _(u"Thank you for your message.") 27 | form_invalid_message = _(u"There was an error in the contact form.") 28 | 29 | See the :ref:`customization section ` on how to plug 30 | the subclassed view into your URLconf. 31 | 32 | Check out `Django messages documentation`_ to make sure messages are enabled in your project. 33 | 34 | .. _`Django messages documentation`: https://docs.djangoproject.com/en/dev/ref/contrib/messages/#enabling-messages 35 | 36 | Bootstrap integration 37 | ===================== 38 | 39 | Embedding the contact form 40 | -------------------------- 41 | 42 | From our personal experience with `Bootstrap`_-powered websites, the easiest 43 | way to embed the contact form is to use `django-crispy-forms`_. Install it 44 | with:: 45 | 46 | pip install django-crispy-forms 47 | 48 | and add ``crispy_forms`` to ``INSTALLED_APPS``. From there it's as simple as 49 | adding a ``crispy`` template tag to display the form. For example: 50 | 51 | .. code-block:: html+django 52 | 53 | {% load envelope_tags crispy_forms_tags %} 54 | 55 | ... 56 | 57 |
58 | {% csrf_token %} 59 | {% antispam_fields %} 60 | {% crispy form %} 61 |
62 | 63 | .. _`Bootstrap`: http://getbootstrap.com/ 64 | .. _`django-crispy-forms`: https://github.com/maraujop/django-crispy-forms 65 | 66 | To add a submit button, create a custom form using ``django-crispy-forms`` helper:: 67 | 68 | # forms.py 69 | from envelope.forms import ContactForm 70 | from crispy_forms.helper import FormHelper 71 | from crispy_forms.layout import Submit 72 | 73 | 74 | class MyContactForm(ContactForm): 75 | def __init__(self, *args, **kwargs): 76 | super(MyContactForm, self).__init__(*args, **kwargs) 77 | self.helper = FormHelper() 78 | self.helper.add_input(Submit('submit', 'Submit', css_class='btn-lg')) 79 | 80 | And finally link this form to your view:: 81 | 82 | # views.py 83 | from braces.views import FormMessagesMixin 84 | from envelope.views import ContactView 85 | 86 | from django.utils.translation import ugettext_lazy as _ 87 | 88 | from .forms import MyContactForm 89 | 90 | 91 | class MyContactView(FormMessagesMixin, ContactView): 92 | form_invalid_message = _(u"There was an error in the contact form.") 93 | form_valid_message = _(u"Thank you for your message.") 94 | form_class = MyContactForm 95 | 96 | or just use it in your urls.py if you directly reference :class:`~envelope.views.ContactView` ``as_view()`` method:: 97 | 98 | # urls.py 99 | from django.conf.urls import patterns, url 100 | from envelope.views import ContactView 101 | 102 | from .forms import MyContactForm 103 | 104 | 105 | urlpatterns = patterns('', 106 | url(r'^contact/', ContactView.as_view(form_class=MyContactForm)), 107 | ) 108 | 109 | Displaying form messages nicely 110 | ------------------------------- 111 | 112 | GETting the contact form page after POSTing it will give you access to either a success message (form_valid_message) 113 | or an error message (form_invalid_message) thanks to django-braces' ``FormMessagesMixin``. These messages use 114 | `Django messages tag level`_ so you can use the right Bootstrap class. 115 | 116 | .. _`Django messages tag level`: https://docs.djangoproject.com/en/dev/ref/contrib/messages/#message-tags 117 | 118 | We recommend you first override Django's default message tags as following:: 119 | 120 | # settings.py 121 | MESSAGE_TAGS = { 122 | messages.DEBUG: 'debug', 123 | messages.INFO: 'info', 124 | messages.SUCCESS: 'success', 125 | messages.WARNING: 'warning', 126 | messages.ERROR: 'danger' # 'error' by default 127 | } 128 | 129 | Then you can use `Django's tip`_ to display messages with Bootstrap CSS classes such as text-info or alert-warning: 130 | 131 | .. _`Django's tip`: https://docs.djangoproject.com/en/dev/ref/contrib/messages/#displaying-messages 132 | 133 | .. code-block:: html+django 134 | 135 | {% if messages %} 136 |
    137 | {% for message in messages %} 138 |
  • 139 | {{ message }} 140 |
  • 141 | {% endfor %} 142 |
143 | {% endif %} 144 | 145 | Categorized contact form 146 | ======================== 147 | 148 | Although the ``category`` field was removed from the default form class in 149 | 1.0, you can bring it back to your form using the following subclass:: 150 | 151 | from envelope.forms import ContactForm 152 | 153 | from django import forms 154 | from django.utils.translation import ugettext_lazy as _ 155 | 156 | 157 | class CategorizedContactForm(ContactForm): 158 | CATEGORY_CHOICES = ( 159 | ('', _("Choose")), 160 | (10, _("A general question regarding the website")), 161 | # ... any other choices you can imagine 162 | (None, _("Other")), 163 | ) 164 | category = forms.ChoiceField(label=_("Category"), choices=CATEGORY_CHOICES) 165 | 166 | def __init__(self, *args, **kwargs): 167 | """ 168 | Category choice will be rendered above the subject field. 169 | """ 170 | super(CategorizedContactForm, self).__init__(*args, **kwargs) 171 | self.fields.keyOrder = [ 172 | 'sender', 'email', 'category', 'subject', 'message', 173 | ] 174 | 175 | def get_context(self): 176 | """ 177 | Adds full category description to template variables in order 178 | to display the category in email body. 179 | """ 180 | context = super(CategorizedContactForm, self).get_context() 181 | context['category'] = self.get_category_display() 182 | return context 183 | 184 | def get_category_display(self): 185 | """ 186 | Returns the displayed name of the selected category. 187 | """ 188 | try: 189 | category = int(self.cleaned_data['category']) 190 | except (AttributeError, ValueError, KeyError): 191 | category = None 192 | return dict(self.CATEGORY_CHOICES).get(category) 193 | -------------------------------------------------------------------------------- /docs/customization.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Customization 3 | ============= 4 | 5 | Most of the time, including ``envelope.urls`` is just fine. But if you want more 6 | control over the contact form, you need to hook the view into your URLconf 7 | yourself. Just import :class:`envelope.views.ContactView`, and call the 8 | ``as_view`` classmethod when defining URL patterns. 9 | 10 | Example:: 11 | 12 | # urls.py 13 | from django.conf.urls import patterns, url 14 | from envelope.views import ContactView 15 | 16 | urlpatterns = patterns('', 17 | url(r'^contact/', ContactView.as_view()), 18 | ) 19 | 20 | .. _subclassing-contact-view: 21 | 22 | If you want some more fine-grained control over the contact form, you can 23 | customize the view class. You can inherit from :class:`envelope.views.ContactView` 24 | and set class attributes in your derived view class, or simply pass 25 | the values for these attributes when calling ``as_view`` in your URLconf. 26 | 27 | Example (using a subclass):: 28 | 29 | # some_app/views.py 30 | from envelope.views import ContactView 31 | 32 | class MyContactView(ContactView): 33 | template_name = "my_contact.html" 34 | success_url = "/thank/you/kind/sir/" 35 | 36 | # urls.py 37 | from django.conf.urls import patterns, url 38 | from some_app.views import MyContactView 39 | 40 | urlpatterns = patterns('', 41 | url(r'^contact/', MyContactView.as_view()), 42 | ) 43 | 44 | Example (setting attributes in place):: 45 | 46 | # urls.py 47 | from django.conf.urls import patterns, url 48 | from envelope.views import ContactView 49 | 50 | urlpatterns = patterns('', 51 | url(r'^contact/', ContactView.as_view( 52 | template_name="my_contact.html", 53 | success_url="/thank/you/kind/sir/" 54 | )), 55 | ) 56 | 57 | The following options (as well as those already in Django's `FormView`_) are recognized by the view: 58 | 59 | * ``form_class``: Which form class to use for contact message handling. 60 | The default (:class:`envelope.forms.ContactForm`) is often enough, but you can subclass it 61 | if you want, or even replace with a totally custom class. The only requirement is 62 | that your custom class has a ``save()`` method which should send the message 63 | somewhere. Stick to the default, or its subclasses. 64 | 65 | * ``template_name``: Full name of the template which will display the form. By 66 | default it is ``envelope/contact.html``. 67 | 68 | * ``success_url``: View name or a hardcoded URL of the page with some kind of a 69 | "thank you for your feedback", displayed after the form is successfully 70 | submitted. If left unset, the view redirects to itself. 71 | 72 | * ``form_kwargs``: Additional kwargs to be used in the creation of the form. Use with :class:`envelope.forms.ContactForm` form arguments for dynamic customization of the form. 73 | 74 | You can also subclass :class:`envelope.forms.ContactForm` to further customize 75 | your form processing. Either set the following options as keyword arguments to 76 | ``__init__``, or override class attributes. 77 | 78 | * ``subject_intro``: Prefix used to create the subject line. Default is ``settings.ENVELOPE_SUBJECT_INTRO``. 79 | 80 | * ``from_email``: Used in the email from. Defaults to ``settings.DEFAULT_FROM_EMAIL``. 81 | 82 | * ``email_recipients``: List of email addresses to send the email to. Defaults to ``settings.ENVELOPE_EMAIL_RECIPIENTS``. 83 | 84 | * ``template_name``: Template used to render the plaintext email message. Defaults to ``envelope/email_body.txt``. You can use any of the form field names as template variables. 85 | 86 | * ``html_template_name``: Template used to render the HTML email message. Defaults to ``envelope/email_body.html``. 87 | 88 | Example of a custom form:: 89 | 90 | # forms.py 91 | from envelope.forms import ContactForm 92 | 93 | class MyContactForm(ContactForm): 94 | subject_intro = "URGENT: " 95 | template_name = "plaintext_email.txt" 96 | html_template_name = "contact_email.html" 97 | 98 | # urls.py 99 | from django.conf.urls import patterns, url 100 | from envelope.views import ContactView 101 | from forms import MyContactForm 102 | 103 | urlpatterns = patterns('', 104 | url(r'^contact/', ContactView.as_view(form_class=MyContactForm)), 105 | ) 106 | 107 | 108 | .. _`FormView`: https://docs.djangoproject.com/en/dev/ref/class-based-views/#django.views.generic.edit.FormView 109 | 110 | -------------------------------------------------------------------------------- /docs/development.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Development 3 | =========== 4 | 5 | Contributing 6 | ============ 7 | 8 | Report bugs 9 | ----------- 10 | 11 | Use the `issue tracker`_ on GitHub to file bugs. 12 | 13 | Hack on the code 14 | ---------------- 15 | 16 | Fork the repository on GitHub, do your work in your fork (rhymes, eh?) 17 | and send me a pull request. Try to conform to :pep:`8` and make sure 18 | the tests pass (see below). 19 | 20 | 21 | Running tests 22 | ============= 23 | 24 | .. note:: 25 | It is recommended to work in a virtualenv_. 26 | 27 | All dependencies required for running tests are specified in the file 28 | ``test_requirements.txt``. 29 | 30 | .. note:: 31 | If you get errors such as ``ImportError: No module named mock`` while 32 | running tests, you're probably on Python 2 (Python 3 has ``mock`` in 33 | standard library). To fix that, run ``pip install mock``. 34 | 35 | To get the tests up and running, follow these commands:: 36 | 37 | virtualenv envelope 38 | cd envelope 39 | source bin/activate 40 | git clone https://github.com/zsiciarz/django-envelope.git 41 | cd django-envelope 42 | pip install -r test_requirements.txt 43 | make test 44 | 45 | .. note:: 46 | First three steps can be simplified by using virtualenvwrapper_. 47 | 48 | To get a coverage report, replace the last command with:: 49 | 50 | make coverage 51 | 52 | 53 | CI Server 54 | ========= 55 | 56 | The GitHub repository is hooked to `Travis CI`_. Travis worker pushes code 57 | coverage to `coveralls.io`_ after each successful build. 58 | 59 | 60 | .. _`issue tracker`: https://github.com/zsiciarz/django-envelope/issues 61 | .. _virtualenv: http://www.virtualenv.org/ 62 | .. _virtualenvwrapper: http://www.doughellmann.com/projects/virtualenvwrapper/ 63 | .. _`Travis CI`: https://travis-ci.org/zsiciarz/django-envelope 64 | .. _`coveralls.io`: https://coveralls.io/r/zsiciarz/django-envelope 65 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-envelope documentation master file, created by 2 | sphinx-quickstart on Wed Jun 1 18:45:24 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. include:: ../README.rst 7 | 8 | Gratipay 9 | -------- 10 | 11 | Like this project? You can support it via `Gratipay`_! 12 | 13 | .. _`Gratipay`: https://www.gratipay.com/zsiciarz 14 | 15 | Documentation 16 | ------------- 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | 21 | install 22 | usage 23 | configuration 24 | customization 25 | cookbook 26 | development 27 | reference 28 | changes 29 | 30 | 31 | Indices and tables 32 | ================== 33 | 34 | * :ref:`genindex` 35 | * :ref:`modindex` 36 | * :ref:`search` 37 | 38 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | Make sure you have Django installed. Then install the package from PyPI:: 6 | 7 | pip install django-envelope 8 | 9 | If you like living on the edge, grab the development version from Github_:: 10 | 11 | git clone https://github.com/zsiciarz/django-envelope.git 12 | cd django-envelope 13 | python setup.py install 14 | 15 | To enable a simple antispam check, install `django-honeypot`_. Envelope will 16 | automatically pick that one up and use in the contact form. 17 | 18 | .. _Github: https://github.com/zsiciarz/django-envelope 19 | .. _`django-honeypot`: https://github.com/sunlightlabs/django-honeypot/ 20 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-envelope.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-envelope.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/reference.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Reference 3 | ========= 4 | 5 | Views 6 | ===== 7 | 8 | .. automodule:: envelope.views 9 | :members: 10 | 11 | Forms 12 | ===== 13 | 14 | .. automodule:: envelope.forms 15 | :members: 16 | 17 | Template tags 18 | ============= 19 | 20 | Add ``{% load envelope_tags %}`` to your template before using any of these. 21 | 22 | .. automodule:: envelope.templatetags.envelope_tags 23 | :members: 24 | 25 | Spam filters 26 | ============ 27 | 28 | .. automodule:: envelope.spam_filters 29 | :members: 30 | 31 | Signals 32 | ======= 33 | 34 | ``before_send`` 35 | 36 | Sent after the form is submitted and valid, but before sending the message. 37 | 38 | Arguments: 39 | 40 | ``sender`` 41 | View class. 42 | 43 | ``request`` 44 | The current request object. 45 | 46 | ``form`` 47 | The form object (already valid, so ``cleaned_data`` is available). 48 | 49 | ``after_send`` 50 | 51 | This signal is sent after sending the message. 52 | 53 | Arguments: 54 | 55 | ``sender`` 56 | Form class. 57 | 58 | ``message`` 59 | An instance of :class:`EmailMessage ` that was used to send the message. 60 | 61 | ``form`` 62 | The form object. 63 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | Add ``envelope`` to your ``INSTALLED_APPS`` in ``settings.py``. The application 6 | does not define any models, so a ``manage.py syncdb`` is *not needed*. If you 7 | installed ``django-honeypot``, add also ``honeypot`` to ``INSTALLED_APPS``. 8 | 9 | For a quick start, simply include the app's ``urls.py`` in your main URLconf, like 10 | this:: 11 | 12 | urlpatterns = patterns('', 13 | #... 14 | (r'^contact/', include('envelope.urls')), 15 | #... 16 | ) 17 | 18 | The view that you just hooked into your URLconf will try to render a 19 | ``envelope/contact.html`` template. Create that file in some location 20 | where Django would be able to find it (see the `Django template docs`_ 21 | for details). 22 | 23 | .. note:: 24 | .. versionchanged:: 1.0 25 | ``django-envelope`` used to ship with one such template by default. 26 | However, it made too opinionated assumptions about your templates and 27 | site layout. For that reason it was removed and you *must* now create 28 | the template explicitly. 29 | 30 | This template file can (and possibly should) extend your base site template. 31 | The view will pass to the context a ``form`` variable, which is an instance 32 | of :class:`~envelope.forms.ContactForm`. You can write your own HTML code 33 | for the form or use the provided ``{% render_contact_form %}`` template tag 34 | for simplicity. For example (assuming ``base.html`` is your main template): 35 | 36 | .. code-block:: html+django 37 | 38 | {% extends "base.html" %} 39 | {% load envelope_tags %} 40 | 41 | {% block content %} 42 | {% render_contact_form %} 43 | {% endblock %} 44 | 45 | That's basically it. Navigate to the given URL and see the contact form in 46 | action. See :doc:`customization` for more customization options. 47 | 48 | .. _`Django template docs`: https://docs.djangoproject.com/en/dev/ref/templates/api/#loading-templates 49 | -------------------------------------------------------------------------------- /envelope/__init__.py: -------------------------------------------------------------------------------- 1 | __version_info__ = (1, 4, 0, 'final', 0) 2 | 3 | 4 | def get_version(): 5 | version = '%s.%s' % (__version_info__[0], __version_info__[1]) 6 | if __version_info__[2]: 7 | version = '%s.%s' % (version, __version_info__[2]) 8 | if __version_info__[3] != 'final': 9 | version = '%s%s' % (version, __version_info__[3]) 10 | if __version_info__[4]: 11 | version = '%s%s' % (version, __version_info__[4]) 12 | return version 13 | 14 | 15 | __version__ = get_version() 16 | -------------------------------------------------------------------------------- /envelope/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | """ 6 | Contact form class definitions. 7 | """ 8 | 9 | import logging 10 | from smtplib import SMTPException 11 | 12 | from django import forms 13 | from django.core import mail 14 | from django.template.loader import render_to_string 15 | from django.utils.translation import ugettext_lazy as _ 16 | 17 | from envelope import settings 18 | from envelope.signals import after_send 19 | 20 | 21 | logger = logging.getLogger('envelope.forms') 22 | 23 | 24 | class ContactForm(forms.Form): 25 | """ 26 | Base contact form class. 27 | 28 | The following form attributes can be overridden when creating the 29 | form or in a subclass. If you need more flexibility, you can instead 30 | override the associated methods such as `get_from_email()` (see below). 31 | 32 | ``subject_intro`` 33 | Prefix used to create the subject line. Default is 34 | ``settings.ENVELOPE_SUBJECT_INTRO``. 35 | 36 | ``from_email`` 37 | Used in the email from. Defaults to 38 | ``settings.ENVELOPE_FROM_EMAIL``. 39 | 40 | ``email_recipients`` 41 | List of email addresses to send the email to. Defaults to 42 | ``settings.ENVELOPE_EMAIL_RECIPIENTS``. 43 | 44 | ``template_name`` 45 | Template used to render the (plaintext) email message. Defaults to 46 | ``envelope/email_body.txt``. 47 | 48 | ``html_template_name`` 49 | Template used to render the HTML email message. Defaults to 50 | ``envelope/email_body.html``. 51 | 52 | """ 53 | sender = forms.CharField(label=_("From")) 54 | email = forms.EmailField(label=_("Email")) 55 | subject = forms.CharField(label=_("Subject"), required=False) 56 | message = forms.CharField(label=_("Message"), widget=forms.Textarea()) 57 | 58 | subject_intro = settings.SUBJECT_INTRO 59 | from_email = settings.FROM_EMAIL 60 | email_recipients = settings.EMAIL_RECIPIENTS 61 | template_name = 'envelope/email_body.txt' 62 | html_template_name = 'envelope/email_body.html' 63 | 64 | def __init__(self, *args, **kwargs): 65 | for kwarg in list(kwargs): 66 | if hasattr(self, kwarg): 67 | setattr(self, kwarg, kwargs.pop(kwarg)) 68 | super(ContactForm, self).__init__(*args, **kwargs) 69 | 70 | def save(self): 71 | """ 72 | Sends the message. 73 | """ 74 | subject = self.get_subject() 75 | from_email = self.get_from_email() 76 | email_recipients = self.get_email_recipients() 77 | context = self.get_context() 78 | message_body = render_to_string(self.get_template_names(), context) 79 | try: 80 | message = mail.EmailMultiAlternatives( 81 | subject=subject, 82 | body=message_body, 83 | from_email=from_email, 84 | to=email_recipients, 85 | headers={ 86 | 'Reply-To': self.cleaned_data['email'] 87 | } 88 | ) 89 | if settings.USE_HTML_EMAIL: 90 | html_body = render_to_string(self.html_template_name, context) 91 | message.attach_alternative(html_body, "text/html") 92 | message.send() 93 | after_send.send(sender=self.__class__, message=message, form=self) 94 | logger.info(_("Contact form submitted and sent (from: %s)") % 95 | self.cleaned_data['email']) 96 | except SMTPException: 97 | logger.exception(_("An error occured while sending the email")) 98 | return False 99 | else: 100 | return True 101 | 102 | def get_context(self): 103 | """ 104 | Returns context dictionary for the email body template. 105 | 106 | By default, the template has access to all form fields' values 107 | stored in ``self.cleaned_data``. Override this method to set 108 | additional template variables. 109 | """ 110 | return self.cleaned_data.copy() 111 | 112 | def get_subject(self): 113 | """ 114 | Returns a string to be used as the email subject. 115 | 116 | Override this method to customize the display of the subject. 117 | """ 118 | return self.subject_intro + self.cleaned_data['subject'] 119 | 120 | def get_from_email(self): 121 | """ 122 | Returns the from email address. 123 | 124 | Override to customize how the from email address is determined. 125 | """ 126 | return self.from_email 127 | 128 | def get_email_recipients(self): 129 | """ 130 | Returns a list of recipients for the message. 131 | 132 | Override to customize how the email recipients are determined. 133 | """ 134 | return self.email_recipients 135 | 136 | def get_template_names(self): 137 | """ 138 | Returns a template_name (or list of template_names) to be used 139 | for the email message. 140 | 141 | Override to use your own method choosing a template name. 142 | """ 143 | return self.template_name 144 | -------------------------------------------------------------------------------- /envelope/locale/el/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/envelope/locale/el/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /envelope/locale/el/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2017-03-08 15:21+0200\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | #: envelope/forms.py:53 21 | msgid "From" 22 | msgstr "Αποστολέας" 23 | 24 | #: envelope/forms.py:54 25 | msgid "Email" 26 | msgstr "Email" 27 | 28 | #: envelope/forms.py:55 29 | msgid "Subject" 30 | msgstr "Θέμα" 31 | 32 | #: envelope/forms.py:56 33 | msgid "Message" 34 | msgstr "Μήνυμα" 35 | 36 | #: envelope/forms.py:94 37 | #, python-format 38 | msgid "Contact form submitted and sent (from: %s)" 39 | msgstr "Η Φόρμα Επικοινωνίας απεστάλη επιτυχώς (αποστολέας: %s)" 40 | 41 | #: envelope/forms.py:97 42 | msgid "An error occured while sending the email" 43 | msgstr "Υπήρξε λάθος κατά την αποστολή του email" 44 | 45 | #: envelope/settings.py:19 46 | msgid "Message from contact form: " 47 | msgstr "Μήνυμα από τη Φόρμα Επικοινωνίας: " 48 | 49 | #: envelope/templates/envelope/contact_form.html:14 50 | msgid "Send!" 51 | msgstr "Αποστολή!" 52 | 53 | #: envelope/templates/envelope/email_body.html:6 54 | #: envelope/templates/envelope/email_body.html:34 55 | #: envelope/templates/envelope/email_body.txt:2 56 | msgid "Message from the contact form" 57 | msgstr "Μήνυμα από τη Φόρμα Επικοινωνίας" 58 | 59 | #: envelope/templates/envelope/email_body.html:78 60 | #: envelope/templates/envelope/email_body.txt:3 61 | msgid "Sender" 62 | msgstr "Αποστολέας" 63 | 64 | #: envelope/templates/envelope/email_body.html:103 65 | #: envelope/templates/envelope/email_body.txt:10 66 | msgid "message sent with envelope - a contact form app for Django" 67 | msgstr "το μήνυμα εστάλη με το envelope" 68 | 69 | #: envelope/templates/envelope/email_body.txt:4 70 | msgid "The message follows." 71 | msgstr "Ακολουθεί το μήνυμα." 72 | -------------------------------------------------------------------------------- /envelope/locale/es/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/envelope/locale/es/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /envelope/locale/es/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2016-08-31 10:46+0200\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #: forms.py:53 22 | msgid "From" 23 | msgstr "De" 24 | 25 | #: forms.py:54 26 | msgid "Email" 27 | msgstr "Correo electrónico" 28 | 29 | #: forms.py:55 30 | msgid "Subject" 31 | msgstr "Asunto" 32 | 33 | #: forms.py:56 34 | msgid "Message" 35 | msgstr "Mensaje" 36 | 37 | #: forms.py:94 38 | #, python-format 39 | msgid "Contact form submitted and sent (from: %s)" 40 | msgstr "Formulario de contacto procesado y enviado (desde: %s)" 41 | 42 | #: forms.py:97 43 | msgid "An error occured while sending the email" 44 | msgstr "Ocurrió un error al enviar el e-mail" 45 | 46 | #: settings.py:19 47 | msgid "Message from contact form: " 48 | msgstr "Mensaje del formulario de contacto: " 49 | 50 | #: templates/envelope/contact_form.html:14 51 | msgid "Send!" 52 | msgstr "¡Enviar!" 53 | 54 | #: templates/envelope/email_body.html:6 templates/envelope/email_body.html:34 55 | #: templates/envelope/email_body.txt:2 56 | msgid "Message from the contact form" 57 | msgstr "Mensaje del formulario de contacto" 58 | 59 | #: templates/envelope/email_body.html:78 templates/envelope/email_body.txt:3 60 | msgid "Sender" 61 | msgstr "Remitente" 62 | 63 | #: templates/envelope/email_body.html:103 templates/envelope/email_body.txt:10 64 | msgid "message sent with envelope - a contact form app for Django" 65 | msgstr "mensaje enviado con envelope - una app de formulario de contacto para Django" 66 | 67 | #: templates/envelope/email_body.txt:4 68 | msgid "The message follows." 69 | msgstr "El mensaje a continuación." 70 | -------------------------------------------------------------------------------- /envelope/locale/fi/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/envelope/locale/fi/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /envelope/locale/fi/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # Finnish translations for django-envelope. 2 | # Copyright (C) 2011 Antti Kaihola (original author) and contributors 3 | # This file is distributed under the same license as the django-envelope package. 4 | # Antti Kaihola , 2011. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: django-envelope 0.2.1\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2011-01-24 16:03+0200\n" 11 | "PO-Revision-Date: 2011-01-24 16:10+0200\n" 12 | "Last-Translator: Antti Kaihola \n" 13 | "Language-Team: Finnish \n" 14 | "Language: \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | 20 | #: forms.py:20 21 | msgid "Choose" 22 | msgstr "Valitse" 23 | 24 | #: forms.py:21 25 | msgid "A general question regarding the website" 26 | msgstr "Yleisesti sivustoa koskeva kysymys" 27 | 28 | #: forms.py:22 29 | msgid "Other" 30 | msgstr "Muu" 31 | 32 | #: forms.py:33 33 | msgid "From" 34 | msgstr "Lähettäjä" 35 | 36 | #: forms.py:34 37 | msgid "Email" 38 | msgstr "Sähköposti" 39 | 40 | #: forms.py:35 41 | msgid "Subject" 42 | msgstr "Aihe" 43 | 44 | #: forms.py:36 45 | msgid "Message" 46 | msgstr "Viesti" 47 | 48 | #: forms.py:43 49 | msgid "Message from contact form: " 50 | msgstr "Viesti palautelomakkeesta: " 51 | 52 | #: forms.py:51 53 | #, python-format 54 | msgid "Contact form submitted and sent (from: %s)" 55 | msgstr "Lomake lähetetty (lähettäjä: %s)" 56 | 57 | #: forms.py:53 58 | #, python-format 59 | msgid "Contact form error (%s)" 60 | msgstr "Palautelomakkeen virhe (%s)" 61 | 62 | #: forms.py:62 63 | msgid "ContactForm.send() is deprecated, use save() instead" 64 | msgstr "ContactForm.send() poistuu käytöstä, käytä sen sijaan save()" 65 | 66 | #: forms.py:88 67 | msgid "Category" 68 | msgstr "Kategoria" 69 | 70 | #: views.py:54 71 | msgid "Thank you for your message." 72 | msgstr "Kiitos viestistäsi." 73 | 74 | #: views.py:59 75 | msgid "There was en error in the contact form." 76 | msgstr "Palautelomake aiheutti virheen." 77 | 78 | #: templates/envelope/contact.html:7 79 | msgid "Contact" 80 | msgstr "Palaute" 81 | 82 | #: templates/envelope/contact.html:18 83 | msgid "Send!" 84 | msgstr "Lähetä!" 85 | -------------------------------------------------------------------------------- /envelope/locale/fr/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/envelope/locale/fr/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /envelope/locale/fr/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: PACKAGE VERSION\n" 9 | "Report-Msgid-Bugs-To: \n" 10 | "POT-Creation-Date: 2010-11-24 14:42+0100\n" 11 | "PO-Revision-Date: 2010-11-25 20:29+0100\n" 12 | "Last-Translator: Ewelina Srebro \n" 13 | "Language-Team: LANGUAGE \n" 14 | "MIME-Version: 1.0\n" 15 | "Content-Type: text/plain; charset=UTF-8\n" 16 | "Content-Transfer-Encoding: 8bit\n" 17 | 18 | #: .\forms.py:20 19 | msgid "Choose" 20 | msgstr "Choisissez" 21 | 22 | #: .\forms.py:21 23 | msgid "A general question regarding the website" 24 | msgstr "Question générale concernant le site" 25 | 26 | #: .\forms.py:22 27 | msgid "Other" 28 | msgstr "Autres" 29 | 30 | #: .\forms.py:33 31 | msgid "From" 32 | msgstr "De" 33 | 34 | #: .\forms.py:34 35 | msgid "Email" 36 | msgstr "E-mail" 37 | 38 | #: .\forms.py:35 39 | msgid "Subject" 40 | msgstr "Sujet" 41 | 42 | #: .\forms.py:36 43 | msgid "Message" 44 | msgstr "Message" 45 | 46 | #: .\forms.py:43 47 | msgid "Message from contact form: " 48 | msgstr "Message du formulaire de contact : " 49 | 50 | #: .\forms.py:51 51 | #, python-format 52 | msgid "Contact form submitted and sent (from: %s)" 53 | msgstr "Formulaire de contact soumis et envoyé (de %s)" 54 | 55 | #: .\forms.py:53 56 | #, python-format 57 | msgid "Contact form error (%s)" 58 | msgstr "Message d'erreur du formulaire (%s)" 59 | 60 | #: .\forms.py:62 61 | msgid "ContactForm.send() is deprecated, use save() instead" 62 | msgstr "ContactForm.send() est obsolète, utilisez plutôt save()" 63 | 64 | 65 | #: .\forms.py:88 66 | msgid "Category" 67 | msgstr "Catégorie" 68 | 69 | #: .\views.py:54 70 | msgid "Thank you for your message." 71 | msgstr "Merci pour votre message." 72 | 73 | #: .\views.py:59 74 | msgid "There was en error in the contact form." 75 | msgstr "Il y a une erreur dans le formulaire de contact." 76 | 77 | #: .\templates\envelope\contact.html.py:7 78 | msgid "Contact" 79 | msgstr "Contact" 80 | 81 | #: .\templates\envelope\contact.html.py:18 82 | msgid "Send!" 83 | msgstr "Envoyer !" 84 | -------------------------------------------------------------------------------- /envelope/locale/lv/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/envelope/locale/lv/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /envelope/locale/lv/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2016-04-19 15:51+0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : " 20 | "2);\n" 21 | 22 | #: forms.py:53 23 | msgid "From" 24 | msgstr "No" 25 | 26 | #: forms.py:54 27 | msgid "Email" 28 | msgstr "E-pasts" 29 | 30 | #: forms.py:55 31 | msgid "Subject" 32 | msgstr "Temats" 33 | 34 | #: forms.py:56 35 | msgid "Message" 36 | msgstr "Ziņa" 37 | 38 | #: forms.py:94 39 | #, python-format 40 | msgid "Contact form submitted and sent (from: %s)" 41 | msgstr "Ziņa saņemta un nosūtīta (no: %s)" 42 | 43 | #: forms.py:97 44 | msgid "An error occured while sending the email" 45 | msgstr "Notikusi kļūme mēģinot nosūtīt e-pastu" 46 | 47 | #: settings.py:19 48 | msgid "Message from contact form: " 49 | msgstr "Ziņa no kontaktformas: " 50 | 51 | #: templates/envelope/contact_form.html:14 52 | msgid "Send!" 53 | msgstr "Nosūtīt!" 54 | 55 | #: templates/envelope/email_body.html:6 templates/envelope/email_body.html:34 56 | #: templates/envelope/email_body.txt:2 57 | msgid "Message from the contact form" 58 | msgstr "Ziņa no kontaktformas" 59 | 60 | #: templates/envelope/email_body.html:78 templates/envelope/email_body.txt:3 61 | msgid "Sender" 62 | msgstr "Sūtītājs" 63 | 64 | #: templates/envelope/email_body.html:103 templates/envelope/email_body.txt:10 65 | msgid "message sent with envelope - a contact form app for Django" 66 | msgstr "ziņa nosūtīta ar envelope - kontaktformas aplikācija priekš Django" 67 | 68 | #: templates/envelope/email_body.txt:4 69 | msgid "The message follows." 70 | msgstr "Ziņa." 71 | -------------------------------------------------------------------------------- /envelope/locale/nl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/envelope/locale/nl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /envelope/locale/nl/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # Dutch translation for django-envelope. 2 | # Copyright (C) 2015 Antti Kaihola (original author) and contributors 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # Jan Pieter Waagmeester , 2015. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: django-envelope 1.0.0\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2015-04-30 22:56+0200\n" 12 | "PO-Revision-Date: 2O15-04-30 22:56+0200\n" 13 | "Last-Translator: Jan Pieter Waagmeester \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 20 | 21 | #: envelope/forms.py:53 22 | msgid "From" 23 | msgstr "Afzender" 24 | 25 | #: envelope/forms.py:54 26 | msgid "Email" 27 | msgstr "Email" 28 | 29 | #: envelope/forms.py:55 30 | msgid "Subject" 31 | msgstr "Onderwerp" 32 | 33 | #: envelope/forms.py:56 34 | msgid "Message" 35 | msgstr "Bericht" 36 | 37 | #: envelope/forms.py:94 38 | #, python-format 39 | msgid "Contact form submitted and sent (from: %s)" 40 | msgstr "Contactformulier ingediend en verzonden (van: %s)" 41 | 42 | #: envelope/forms.py:97 43 | msgid "An error occured while sending the email" 44 | msgstr "Er ging iets mis tijdens het verzenden van de email" 45 | 46 | #: envelope/settings.py:19 47 | msgid "Message from contact form: " 48 | msgstr "Bericht van het contactformulier: " 49 | 50 | #: envelope/templates/envelope/contact_form.html:14 51 | msgid "Send!" 52 | msgstr "Verzenden!" 53 | 54 | #: envelope/templates/envelope/email_body.html:6 55 | #: envelope/templates/envelope/email_body.html:34 56 | #: envelope/templates/envelope/email_body.txt:2 57 | msgid "Message from the contact form" 58 | msgstr "Bericht van het contactformulier" 59 | 60 | #: envelope/templates/envelope/email_body.html:78 61 | #: envelope/templates/envelope/email_body.txt:3 62 | msgid "Sender" 63 | msgstr "Afzender" 64 | 65 | #: envelope/templates/envelope/email_body.html:103 66 | #: envelope/templates/envelope/email_body.txt:10 67 | msgid "message sent with envelope - a contact form app for Django" 68 | msgstr "bericht verzonden met envelope - een contactformulier-app voor Django" 69 | 70 | #: envelope/templates/envelope/email_body.txt:4 71 | msgid "The message follows." 72 | msgstr "Het bericht volgt." 73 | -------------------------------------------------------------------------------- /envelope/locale/pl/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/envelope/locale/pl/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /envelope/locale/pl/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: django-envelope 0.2.0\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2010-11-23 21:49+0100\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: Zbigniew Siciarz \n" 14 | "Language-Team: LANGUAGE \n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: .\forms.py:20 20 | msgid "Choose" 21 | msgstr "Wybierz" 22 | 23 | #: .\forms.py:21 24 | msgid "A general question regarding the website" 25 | msgstr "Ogólne zapytanie dotyczące strony" 26 | 27 | #: .\forms.py:22 28 | msgid "Other" 29 | msgstr "Inne" 30 | 31 | #: .\forms.py:33 32 | msgid "From" 33 | msgstr "Od" 34 | 35 | #: .\forms.py:34 36 | msgid "Email" 37 | msgstr "Adres e-mail" 38 | 39 | #: .\forms.py:35 40 | msgid "Subject" 41 | msgstr "Temat" 42 | 43 | #: .\forms.py:36 44 | msgid "Message" 45 | msgstr "Wiadomość" 46 | 47 | #: .\forms.py:43 48 | msgid "Message from contact form: " 49 | msgstr "Wiadomość z formularza kontaktowego:" 50 | 51 | #: .\forms.py:51 52 | #, python-format 53 | msgid "Contact form submitted and sent (from: %s)" 54 | msgstr "Formularz kontaktowy zatwierdzony i wysłany (od: %s)" 55 | 56 | #: .\forms.py:53 57 | #, python-format 58 | msgid "Contact form error (%s)" 59 | msgstr "Błąd formularza kontaktowego (%s)" 60 | 61 | #: .\forms.py:62 62 | msgid "ContactForm.send() is deprecated, use save() instead" 63 | msgstr "ContactForm.send() jest zdeprecjonowane, użyj save()" 64 | 65 | #: .\forms.py:88 66 | #: .\templates\envelope\email_body.txt.py:4 67 | msgid "Category" 68 | msgstr "Kategoria" 69 | 70 | #: .\views.py:54 71 | msgid "Thank you for your message." 72 | msgstr "Dziękujemy za wiadomość." 73 | 74 | #: .\views.py:59 75 | msgid "There was en error in the contact form." 76 | msgstr "Wystąpił błąd w formularzu kontaktowym." 77 | 78 | #: .\templates\envelope\contact.html.py:7 79 | msgid "Contact" 80 | msgstr "Kontakt" 81 | 82 | #: .\templates\envelope\contact.html.py:18 83 | msgid "Send!" 84 | msgstr "Wyślij!" 85 | 86 | #: .\templates\envelope\email_body.txt.py:2 87 | msgid "Message from the contact form" 88 | msgstr "Wiadomość z formularza kontaktowego:" 89 | 90 | #: .\templates\envelope\email_body.txt.py:3 91 | msgid "Sender" 92 | msgstr "Nadawca" 93 | 94 | #: .\templates\envelope\email_body.txt.py:5 95 | msgid "The message follows." 96 | msgstr "Wiadomość znajduje się poniżej." 97 | 98 | #: .\templates\envelope\email_body.txt.py:11 99 | msgid "message sent with envelope - a contact form app for Django" 100 | msgstr "wiadomość wysłana za pomocą envelope - aplikacji formularza kontaktowego dla " 101 | "Django" 102 | 103 | -------------------------------------------------------------------------------- /envelope/locale/pt_BR/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/envelope/locale/pt_BR/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /envelope/locale/pt_BR/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # Alexandre Provencio , 2015. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: django-envelope==1.0 \n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2015-09-08 16:25-0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: Alexandre Provencio \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 20 | 21 | #: forms.py:53 22 | msgid "From" 23 | msgstr "De" 24 | 25 | #: forms.py:54 26 | msgid "Email" 27 | msgstr "Email" 28 | 29 | #: forms.py:55 30 | msgid "Subject" 31 | msgstr "Assunto" 32 | 33 | #: forms.py:56 34 | msgid "Message" 35 | msgstr "Mensagem" 36 | 37 | #: forms.py:94 38 | #, python-format 39 | msgid "Contact form submitted and sent (from: %s)" 40 | msgstr "Formulário de contato submetido e enviado (de: %s)" 41 | 42 | #: forms.py:97 43 | msgid "An error occured while sending the email" 44 | msgstr "Um erro ocorreu ao enviar o email" 45 | 46 | #: settings.py:19 47 | msgid "Message from contact form: " 48 | msgstr "Mensagem do formulário de contato: " 49 | 50 | #: templates/envelope/contact_form.html:14 51 | msgid "Send!" 52 | msgstr "Enviar!" 53 | 54 | #: templates/envelope/email_body.html:6 templates/envelope/email_body.html:34 55 | #: templates/envelope/email_body.txt:2 56 | msgid "Message from the contact form" 57 | msgstr "Mensagem do formulário de contato" 58 | 59 | #: templates/envelope/email_body.html:78 templates/envelope/email_body.txt:3 60 | msgid "Sender" 61 | msgstr "Remetente" 62 | 63 | #: templates/envelope/email_body.html:103 templates/envelope/email_body.txt:10 64 | msgid "message sent with envelope - a contact form app for Django" 65 | msgstr "mensagem enviada com envelope - uma app de formulário de contato para Django" 66 | 67 | #: templates/envelope/email_body.txt:4 68 | msgid "The message follows." 69 | msgstr "A mensagem segue." 70 | -------------------------------------------------------------------------------- /envelope/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/envelope/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /envelope/locale/ru/LC_MESSAGES/django.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2016-04-19 15:51+0300\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" 20 | "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" 21 | 22 | #: forms.py:53 23 | msgid "From" 24 | msgstr "От" 25 | 26 | #: forms.py:54 27 | msgid "Email" 28 | msgstr "E-mail" 29 | 30 | #: forms.py:55 31 | msgid "Subject" 32 | msgstr "Тема" 33 | 34 | #: forms.py:56 35 | msgid "Message" 36 | msgstr "Сообщение" 37 | 38 | #: forms.py:94 39 | #, python-format 40 | msgid "Contact form submitted and sent (from: %s)" 41 | msgstr "Контактный формуляр представляется и послал (от: %s)" 42 | 43 | #: forms.py:97 44 | msgid "An error occured while sending the email" 45 | msgstr "Произошла ошибка при отправке электронной почты" 46 | 47 | #: settings.py:19 48 | msgid "Message from contact form: " 49 | msgstr "Сообщение от контактной формы: " 50 | 51 | #: templates/envelope/contact_form.html:14 52 | msgid "Send!" 53 | msgstr "Послать!" 54 | 55 | #: templates/envelope/email_body.html:6 templates/envelope/email_body.html:34 56 | #: templates/envelope/email_body.txt:2 57 | msgid "Message from the contact form" 58 | msgstr "Сообщение от контактной формы" 59 | 60 | #: templates/envelope/email_body.html:78 templates/envelope/email_body.txt:3 61 | msgid "Sender" 62 | msgstr "Отправитель" 63 | 64 | #: templates/envelope/email_body.html:103 templates/envelope/email_body.txt:10 65 | msgid "message sent with envelope - a contact form app for Django" 66 | msgstr "" 67 | "Сообщение, отправленное с envelope - форма контакта приложение для " 68 | "Django" 69 | 70 | #: templates/envelope/email_body.txt:4 71 | msgid "The message follows." 72 | msgstr "Сообщение." 73 | -------------------------------------------------------------------------------- /envelope/models.py: -------------------------------------------------------------------------------- 1 | # nothing here, move on 2 | -------------------------------------------------------------------------------- /envelope/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | """ 6 | Defaults and overrides for envelope-related settings. 7 | """ 8 | 9 | from django.conf import settings 10 | from django.utils.translation import ugettext_lazy as _ 11 | 12 | 13 | FROM_EMAIL = settings.DEFAULT_FROM_EMAIL 14 | 15 | EMAIL_RECIPIENTS = getattr(settings, 'ENVELOPE_EMAIL_RECIPIENTS', 16 | [settings.DEFAULT_FROM_EMAIL]) 17 | 18 | SUBJECT_INTRO = getattr(settings, 'ENVELOPE_SUBJECT_INTRO', 19 | _("Message from contact form: ")) 20 | 21 | USE_HTML_EMAIL = getattr(settings, 'ENVELOPE_USE_HTML_EMAIL', True) 22 | -------------------------------------------------------------------------------- /envelope/signals.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | """ 6 | Signals sent by the application. 7 | """ 8 | 9 | from django.dispatch import Signal 10 | 11 | before_send = Signal(providing_args=["request", "form"]) 12 | after_send = Signal(providing_args=["message", "form"]) 13 | -------------------------------------------------------------------------------- /envelope/spam_filters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | """ 6 | Functions that reject the message if it is considered spam. 7 | """ 8 | 9 | 10 | def check_honeypot(request, form): 11 | """ 12 | Make sure that the hidden form field is empty, using django-honeypot. 13 | """ 14 | try: 15 | from honeypot.decorators import verify_honeypot_value 16 | return verify_honeypot_value(request, '') is None 17 | except ImportError: # pragma: no cover 18 | return True 19 | -------------------------------------------------------------------------------- /envelope/templates/envelope/contact_form.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load envelope_tags %} 3 | 4 |
5 | {% csrf_token %} 6 | {% antispam_fields %} 7 |
8 | 9 | 10 | {{ form }} 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /envelope/templates/envelope/email_body.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 4 | 5 | 6 | {% trans "Message from the contact form" %} 7 | 8 | 9 | 21 | 22 | 23 | 24 | 25 | 114 | 115 |
26 |
27 | 28 | 29 | 110 | 111 |
30 | 31 | 32 | 38 | 39 |
33 |
34 | {% trans "Message from the contact form" %} 35 |
36 |
37 |
40 | 41 | 42 | 46 | 47 | 48 | 51 | 52 | 53 | 67 | 68 |
43 |
44 | robot picture 45 |
49 | {{ subject }} 50 |
54 |
55 | 56 | 57 | 63 | 64 |
58 |
59 | {{ message }} 60 |
61 |
62 |
65 |
66 |
69 | 70 | 71 | 97 | 98 |
72 |
73 | 74 | 75 | 80 | 85 | 86 |
76 |
77 |
78 | {% trans "Sender" %}: {{ sender }} 79 |
81 |
82 |
83 | {{ email }} 84 |
87 | 88 | 89 | 93 | 94 |
90 |
91 |
92 |
95 |
96 |
99 | 100 | 101 | 107 | 108 |
102 |
103 | {% trans "message sent with envelope - a contact form app for Django" %} 104 |
105 |
106 |
109 |
112 |
113 |
116 | 117 | 118 | -------------------------------------------------------------------------------- /envelope/templates/envelope/email_body.txt: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% trans "Message from the contact form" %} 3 | {% trans "Sender" %}: {{ sender }} ({{ email }}) 4 | {% trans "The message follows." %} 5 | ================================================================================ 6 | {{ message }} 7 | ================================================================================ 8 | 9 | -- 10 | {% trans "message sent with envelope - a contact form app for Django" %} 11 | -------------------------------------------------------------------------------- /envelope/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/envelope/templatetags/__init__.py -------------------------------------------------------------------------------- /envelope/templatetags/envelope_tags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Template tags related to the contact form. 4 | """ 5 | 6 | from __future__ import unicode_literals 7 | 8 | from django import template 9 | 10 | register = template.Library() 11 | 12 | try: 13 | import honeypot 14 | 15 | # Register antispam_fields as an inclusion tag 16 | t = template.Template('{% load honeypot %}{% render_honeypot_field %}') 17 | register.inclusion_tag(t, name='antispam_fields')(lambda: {}) 18 | 19 | except ImportError: # pragma: no cover 20 | # Register antispam_fields as an empty tag 21 | register.simple_tag(name='antispam_fields')(lambda: '') 22 | 23 | 24 | @register.inclusion_tag('envelope/contact_form.html', takes_context=True) 25 | def render_contact_form(context): 26 | """ 27 | Renders the contact form which must be in the template context. 28 | 29 | The most common use case for this template tag is to call it in the 30 | template rendered by :class:`~envelope.views.ContactView`. The template 31 | tag will then render a sub-template ``envelope/contact_form.html``. 32 | """ 33 | if 'form' not in context: 34 | raise template.TemplateSyntaxError( 35 | "There is no 'form' variable in the template context." 36 | ) 37 | return context 38 | -------------------------------------------------------------------------------- /envelope/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from django.conf.urls import url 4 | 5 | from envelope.views import ContactView 6 | 7 | 8 | urlpatterns = [ 9 | url(r'^$', ContactView.as_view(), name='envelope-contact'), 10 | ] 11 | -------------------------------------------------------------------------------- /envelope/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | """ 6 | Views used to process the contact form. 7 | """ 8 | 9 | import logging 10 | 11 | from django.http import HttpResponseBadRequest 12 | from django.shortcuts import redirect 13 | from django.views.generic import FormView 14 | 15 | from envelope import signals 16 | from envelope.forms import ContactForm 17 | 18 | 19 | logger = logging.getLogger('envelope.views') 20 | 21 | 22 | class ContactView(FormView): 23 | """ 24 | Contact form view (class-based). 25 | 26 | Displays the contact form upon a GET request. If the current user is 27 | authenticated, ``sender`` and ``email`` fields are automatically 28 | filled with proper values. 29 | 30 | When the form is submitted and valid, a message is sent and 31 | afterwards the user is redirected to a "thank you" page (by default 32 | it is the page with the form). 33 | 34 | ``form_class`` 35 | Which form class to use for contact message handling. 36 | The default (:class:`envelope.forms.ContactForm`) is often 37 | enough, but you can subclass it if you want, or even replace 38 | with a totally custom class. The only requirement is that your 39 | custom class has a ``save()`` method which should send the 40 | message somewhere. Stick to the default, or its subclasses. 41 | 42 | ``form_kwargs`` 43 | Additional kwargs to be used in the creation of the form. Use 44 | with :class:`envelope.forms.ContactForm` form arguments for 45 | dynamic customization of the form. 46 | 47 | ``template_name`` 48 | Full name of the template which will display 49 | the form. By default it is "envelope/contact.html". 50 | 51 | ``success_url`` 52 | URL of the page with some kind of a "thank you 53 | for your feedback", displayed after the form is successfully 54 | submitted. If left unset, the view redirects to itself. 55 | """ 56 | form_class = ContactForm 57 | form_kwargs = {} 58 | template_name = 'envelope/contact.html' 59 | success_url = None 60 | 61 | def get_success_url(self): 62 | """ 63 | Returns the URL where the view will redirect after submission. 64 | """ 65 | if self.success_url: 66 | return self.success_url 67 | else: 68 | return self.request.get_full_path() 69 | 70 | def get_initial(self): 71 | """ 72 | Automatically fills form fields for authenticated users. 73 | """ 74 | initial = super(ContactView, self).get_initial().copy() 75 | user = self.request.user 76 | if user.is_authenticated: 77 | # the user might not have a full name set in the model 78 | if user.get_full_name(): 79 | sender = '%s (%s)' % (user.get_username(), user.get_full_name()) 80 | else: 81 | sender = user.get_username() 82 | initial.update({ 83 | 'sender': sender, 84 | 'email': user.email, 85 | }) 86 | return initial 87 | 88 | def get_form_kwargs(self): 89 | kwargs = super(ContactView, self).get_form_kwargs() 90 | kwargs.update(self.form_kwargs) 91 | return kwargs 92 | 93 | def form_valid(self, form): 94 | """ 95 | Sends the message and redirects the user to ``success_url``. 96 | """ 97 | responses = signals.before_send.send(sender=self.__class__, 98 | request=self.request, 99 | form=form) 100 | for (receiver, response) in responses: 101 | if not response: 102 | logger.warning("Rejected by %s", receiver.__name__) 103 | return HttpResponseBadRequest() 104 | form.save() 105 | return redirect(self.get_success_url()) 106 | 107 | def form_invalid(self, form): 108 | """ 109 | When the form has errors, display it again. 110 | """ 111 | return self.render_to_response(self.get_context_data(form=form)) 112 | 113 | 114 | def filter_spam(sender, request, form, **kwargs): 115 | """ 116 | Handle spam filtering. 117 | 118 | This function is called when the ``before_send`` signal fires, 119 | passing the current request and form object to the function. 120 | With that information in hand, all available spam filters are called. 121 | 122 | TODO: more spam filters 123 | """ 124 | if issubclass(sender, ContactView): 125 | from envelope.spam_filters import check_honeypot 126 | return check_honeypot(request, form) 127 | 128 | 129 | signals.before_send.connect(filter_spam, 130 | dispatch_uid='envelope.views.filter_spam') 131 | -------------------------------------------------------------------------------- /example_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/example_project/__init__.py -------------------------------------------------------------------------------- /example_project/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /example_project/requirements.txt: -------------------------------------------------------------------------------- 1 | django-braces>=1.0 2 | django-crispy-forms>=1.1 3 | django-honeypot>=0.4 4 | -------------------------------------------------------------------------------- /example_project/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | BASE_DIR = os.path.abspath(os.path.dirname(__file__)) 4 | 5 | DEBUG = False 6 | 7 | ALLOWED_HOSTS = ['*'] 8 | 9 | MANAGERS = ADMINS = () 10 | 11 | DATABASES = { 12 | 'default': { 13 | 'ENGINE': 'django.db.backends.sqlite3', 14 | 'NAME': os.path.join(BASE_DIR, 'example.db'), 15 | 'USER': '', 16 | 'PASSWORD': '', 17 | 'HOST': '', 18 | 'PORT': '', 19 | } 20 | } 21 | 22 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 23 | 24 | DEFAULT_FROM_EMAIL = 'email@example.com' 25 | 26 | TIME_ZONE = 'America/Chicago' 27 | 28 | LANGUAGE_CODE = 'en-us' 29 | 30 | USE_I18N = True 31 | 32 | USE_L10N = True 33 | 34 | SECRET_KEY = 'n5)bgcx7xwk^fhnv+w&qaap)lryz8in*a293=!d=*!%js7^mdr' 35 | 36 | MIDDLEWARE = ( 37 | 'django.contrib.sessions.middleware.SessionMiddleware', 38 | 'django.middleware.common.CommonMiddleware', 39 | 'django.middleware.csrf.CsrfViewMiddleware', 40 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 41 | 'django.contrib.messages.middleware.MessageMiddleware', 42 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 43 | ) 44 | 45 | TEMPLATES = [ 46 | { 47 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 48 | 'APP_DIRS': True, 49 | 'DIRS': ( 50 | os.path.join(BASE_DIR, 'templates'), 51 | ), 52 | 'OPTIONS': { 53 | 'context_processors': ( 54 | "django.template.context_processors.request", 55 | "django.contrib.auth.context_processors.auth", 56 | "django.template.context_processors.debug", 57 | "django.template.context_processors.i18n", 58 | "django.template.context_processors.media", 59 | "django.template.context_processors.static", 60 | "django.template.context_processors.tz", 61 | "django.contrib.messages.context_processors.messages", 62 | ), 63 | 'debug': DEBUG, 64 | }, 65 | } 66 | ] 67 | 68 | ROOT_URLCONF = 'example_project.urls' 69 | 70 | INSTALLED_APPS = ( 71 | 'django.contrib.auth', 72 | 'django.contrib.contenttypes', 73 | 'django.contrib.sessions', 74 | 'django.contrib.messages', 75 | 'envelope', 76 | 'honeypot', 77 | 'crispy_forms', 78 | ) 79 | 80 | HONEYPOT_FIELD_NAME = 'email2' 81 | -------------------------------------------------------------------------------- /example_project/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Contact me! 6 | 7 | 8 | 9 | 10 |
11 | 14 | 19 |
20 |
21 | {% block content %}{% endblock %} 22 |
23 |
24 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /example_project/templates/envelope/contact.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load envelope_tags %} 3 | 4 | {% block default-active %}active{% endblock %} 5 | 6 | {% block content %} 7 |
8 | {% csrf_token %} 9 | {% antispam_fields %} 10 | {{ form.as_p }} 11 | 12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /example_project/templates/envelope/crispy_contact.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load envelope_tags %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block crispy-active %}active{% endblock %} 6 | 7 | {% block content %} 8 |
9 | {% csrf_token %} 10 | {% antispam_fields %} 11 | {{ form|crispy }} 12 | 13 |
14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /example_project/templates/envelope/messages_contact.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load envelope_tags %} 3 | 4 | {% block messages-active %}active{% endblock %} 5 | 6 | {% block content %} 7 | {% if messages %} 8 | {% for message in messages %} 9 |
{{ message }}
10 | {% endfor %} 11 | {% endif %} 12 |
13 | {% csrf_token %} 14 | {% antispam_fields %} 15 | {{ form.as_p }} 16 | 17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /example_project/urls.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.conf.urls import include, url 4 | 5 | from braces.views import FormMessagesMixin 6 | 7 | from envelope.views import ContactView 8 | 9 | 10 | class MessagesContactView(FormMessagesMixin, ContactView): 11 | form_invalid_message = "There was en error in the contact form." 12 | form_valid_message = "Thank you for your message." 13 | template_name = "envelope/messages_contact.html" 14 | 15 | 16 | urlpatterns = [ 17 | url(r'', include('envelope.urls')), 18 | url(r'^crispy/', ContactView.as_view(template_name='envelope/crispy_contact.html'), name='crispy-contact'), 19 | url(r'^messages/', MessagesContactView.as_view(), name='messages-contact'), 20 | ] 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=1.11 2 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | import django 6 | from django.conf import settings 7 | from django.test.utils import get_runner 8 | 9 | if __name__ == "__main__": 10 | os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings' 11 | django.setup() 12 | TestRunner = get_runner(settings) 13 | test_runner = TestRunner() 14 | failures = test_runner.run_tests(["tests"]) 15 | sys.exit(bool(failures)) 16 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 3 | 4 | [aliases] 5 | release = register sdist bdist_egg bdist_wininst upload 6 | 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | 4 | 5 | def read(fname): 6 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 7 | 8 | 9 | setup( 10 | name='django-envelope', 11 | version=__import__('envelope').__version__, 12 | description='A contact form app for Django', 13 | long_description=read('README.rst'), 14 | author='Zbigniew Siciarz', 15 | author_email='zbigniew@siciarz.net', 16 | url='http://github.com/zsiciarz/django-envelope', 17 | download_url='http://pypi.python.org/pypi/django-envelope', 18 | license='MIT', 19 | install_requires=['Django>=1.11'], 20 | packages=find_packages(exclude=['example_project', 'tests']), 21 | include_package_data=True, 22 | classifiers=[ 23 | 'Development Status :: 5 - Production/Stable', 24 | 'Environment :: Web Environment', 25 | 'Framework :: Django', 26 | 'Framework :: Django :: 1.11', 27 | 'Framework :: Django :: 2.2', 28 | 'Framework :: Django :: 3.0', 29 | 'Intended Audience :: Developers', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Operating System :: OS Independent', 32 | 'Programming Language :: Python', 33 | 'Programming Language :: Python :: 2', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.5', 37 | 'Programming Language :: Python :: 3.6', 38 | 'Programming Language :: Python :: 3.7', 39 | 'Programming Language :: Python :: 3.8', 40 | 'Topic :: Utilities', 41 | ], 42 | ) 43 | -------------------------------------------------------------------------------- /test_requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | coverage==4.4.2 3 | tox==2.7.0 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zsiciarz/django-envelope/e16f91f7f58c060e4876f42becef92d7c2564cb6/tests/__init__.py -------------------------------------------------------------------------------- /tests/settings.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | import django 5 | 6 | try: 7 | import honeypot 8 | except ImportError: 9 | honeypot = None 10 | 11 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 12 | 13 | SECRET_KEY = 'thisisntactuallysecretatall' 14 | 15 | ALLOWED_HOSTS = ['*'] 16 | 17 | INSTALLED_APPS = ( 18 | 'django.contrib.auth', 19 | 'django.contrib.contenttypes', 20 | 'django.contrib.sessions', 21 | 'django.contrib.messages', 22 | 'envelope', 23 | ) 24 | 25 | if honeypot: 26 | INSTALLED_APPS += ('honeypot',) 27 | 28 | DATABASES = { 29 | 'default': { 30 | 'ENGINE': 'django.db.backends.sqlite3', 31 | 'NAME': ':memory:', 32 | } 33 | } 34 | 35 | MIDDLEWARE = ( 36 | 'django.contrib.sessions.middleware.SessionMiddleware', 37 | 'django.middleware.common.CommonMiddleware', 38 | 'django.middleware.csrf.CsrfViewMiddleware', 39 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 40 | 'django.contrib.messages.middleware.MessageMiddleware', 41 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 42 | ) 43 | 44 | TEMPLATES = [ 45 | { 46 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 47 | 'APP_DIRS': True, 48 | 'DIRS': ( 49 | os.path.join(BASE_DIR, 'tests', 'templates'), 50 | ), 51 | 'OPTIONS': { 52 | 'context_processors': ( 53 | "django.template.context_processors.request", 54 | "django.contrib.auth.context_processors.auth", 55 | "django.template.context_processors.debug", 56 | "django.template.context_processors.i18n", 57 | "django.template.context_processors.media", 58 | "django.template.context_processors.static", 59 | "django.template.context_processors.tz", 60 | "django.contrib.messages.context_processors.messages", 61 | ), 62 | }, 63 | } 64 | ] 65 | 66 | ROOT_URLCONF = 'tests.urls' 67 | 68 | EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' 69 | 70 | HONEYPOT_FIELD_NAME = 'email2' 71 | 72 | PASSWORD_HASHERS = { 73 | 'django.contrib.auth.hashers.MD5PasswordHasher', 74 | } 75 | 76 | logging.getLogger('envelope').addHandler(logging.NullHandler()) 77 | -------------------------------------------------------------------------------- /tests/templates/customized_contact.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% load i18n %} 3 | 4 | {% load envelope_tags %} 5 | {% block content %} 6 |
7 |

{% trans "Contact" %}

8 |
9 | {% csrf_token %} 10 | {% antispam_fields %} 11 |
12 | 13 | 14 | {{ form }} 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 |
25 | {% endblock %} 26 | 27 | -------------------------------------------------------------------------------- /tests/templates/envelope/contact.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% load i18n %} 3 | 4 | {% load envelope_tags %} 5 | {% block content %} 6 |
7 |

{% trans "Contact" %}

8 | {% render_contact_form %} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /tests/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Contact me! 6 | 7 | 8 | 9 |
10 |
11 |

Contact me!

12 |
    13 | {% for message in messages %} 14 | {{ message }} 15 | {% endfor %} 16 |
17 |
18 | 19 |
20 | {% block content %}{% endblock %} 21 |
22 | 23 | 26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/test_forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | """ 6 | Unit tests for ``django-envelope`` forms. 7 | """ 8 | 9 | import unittest 10 | from smtplib import SMTPException 11 | 12 | try: 13 | from unittest.mock import patch 14 | except ImportError: 15 | from mock import patch 16 | 17 | from envelope.forms import ContactForm 18 | 19 | 20 | class ContactFormTestCase(unittest.TestCase): 21 | """ 22 | Unit tests for ``ContactForm`` class. 23 | """ 24 | 25 | def setUp(self): 26 | self.form_data = { 27 | 'sender': 'me', 28 | 'email': 'test@example.com', 29 | 'subject': 'A subject', 30 | 'message': 'Hello there!', 31 | } 32 | 33 | def test_sender_field(self): 34 | """ 35 | Sender field is required. 36 | """ 37 | self._test_required_field('sender') 38 | 39 | def test_email_field(self): 40 | """ 41 | E-mail field is required. 42 | """ 43 | self._test_required_field('email') 44 | 45 | def test_message_field(self): 46 | """ 47 | Message field is required. 48 | """ 49 | self._test_required_field('message') 50 | 51 | def test_subject_field(self): 52 | """ 53 | Subject field is optional. 54 | """ 55 | del self.form_data['subject'] 56 | form = ContactForm(self.form_data) 57 | self.assertTrue(form.is_valid()) 58 | self.assertNotIn('subject', form.errors) 59 | 60 | def test_all_fields_valid(self): 61 | """ 62 | When all required fields are supplied, the form is valid. 63 | """ 64 | form = ContactForm(self.form_data) 65 | self.assertTrue(form.is_valid()) 66 | 67 | def test_get_context(self): 68 | """ 69 | get_context() returns a copy of form's cleaned_data. 70 | """ 71 | form = ContactForm(self.form_data) 72 | self.assertTrue(form.is_valid()) 73 | context = form.get_context() 74 | self.assertEqual(context, form.cleaned_data) 75 | self.assertIsNot(context, form.cleaned_data) 76 | 77 | def test_save(self): 78 | """ 79 | A call to save() on a valid form sends the message. 80 | """ 81 | form = ContactForm(self.form_data) 82 | self.assertTrue(form.is_valid()) 83 | with patch('django.core.mail.EmailMultiAlternatives') as mock_message: 84 | mock_message.return_value.send.return_value = True 85 | result = form.save() 86 | self.assertTrue(result) 87 | args, kwargs = mock_message.call_args 88 | self.assertIn(self.form_data['subject'], kwargs['subject']) 89 | 90 | def test_html_message_alternative_part(self): 91 | """ 92 | A HTML message is attached by default. 93 | """ 94 | form = ContactForm(self.form_data) 95 | self.assertTrue(form.is_valid()) 96 | with patch('django.core.mail.EmailMultiAlternatives') as mock_message: 97 | form.save() 98 | self.assertTrue(mock_message.return_value.attach_alternative.called) 99 | 100 | def test_init_attr_override(self): 101 | """ 102 | Attributes can be overridden on __init__() 103 | """ 104 | overrides = { 105 | 'subject_intro': 'New subject style: ', 106 | 'from_email': 'new@example.com', 107 | 'email_recipients': ['new_to@example.com'], 108 | } 109 | form = ContactForm(self.form_data, **overrides) 110 | form.is_valid() 111 | with patch('django.core.mail.EmailMultiAlternatives') as mock_message: 112 | mock_message.return_value.send.return_value = True 113 | form.save() 114 | args, kwargs = mock_message.call_args 115 | self.assertIn(overrides['subject_intro'], kwargs['subject']) 116 | self.assertIn(overrides['from_email'], kwargs['from_email']) 117 | self.assertIn(overrides['email_recipients'][0], kwargs['to']) 118 | 119 | def test_save_smtp_error(self): 120 | """ 121 | If the email backend raised an error, the message is not sent. 122 | """ 123 | form = ContactForm(self.form_data) 124 | self.assertTrue(form.is_valid()) 125 | with patch('django.core.mail.EmailMultiAlternatives') as mock_message: 126 | mock_message.return_value.send.side_effect = SMTPException 127 | result = form.save() 128 | self.assertFalse(result) 129 | 130 | def _test_required_field(self, field_name): 131 | """ 132 | Check that the form does not validate without a given field. 133 | """ 134 | del self.form_data[field_name] 135 | form = ContactForm(self.form_data) 136 | self.assertFalse(form.is_valid()) 137 | self.assertIn(field_name, form.errors) 138 | -------------------------------------------------------------------------------- /tests/test_spam_filters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals 4 | 5 | """ 6 | Unit tests for spam filters. 7 | """ 8 | 9 | import unittest 10 | 11 | from django.conf import settings 12 | 13 | try: 14 | import honeypot 15 | except ImportError: 16 | honeypot = None 17 | 18 | from envelope.spam_filters import check_honeypot 19 | 20 | 21 | # mocking form and request, no need to use the real things here 22 | class FakeForm(object): 23 | pass 24 | 25 | 26 | class FakeRequest(object): 27 | def __init__(self): 28 | self.method = 'POST' 29 | self.POST = {} 30 | 31 | 32 | class CheckHoneypotTestCase(unittest.TestCase): 33 | """ 34 | Unit tests for ``check_honeypot`` spam filter. 35 | """ 36 | 37 | def setUp(self): 38 | self.form = FakeForm() 39 | self.request = FakeRequest() 40 | self.honeypot = getattr(settings, 'HONEYPOT_FIELD_NAME', 'email2') 41 | 42 | def test_empty_honeypot(self): 43 | """ 44 | Empty honeypot field is a valid situation. 45 | """ 46 | self.request.POST[self.honeypot] = '' 47 | self.assertTrue(check_honeypot(self.request, self.form)) 48 | 49 | @unittest.skipIf(honeypot is None, "django-honeypot is not installed") 50 | def test_filled_honeypot(self): 51 | """ 52 | A value in the honeypot field is an indicator of a bot request. 53 | """ 54 | self.request.POST[self.honeypot] = 'Hi, this is a bot' 55 | self.assertFalse(check_honeypot(self.request, self.form)) 56 | -------------------------------------------------------------------------------- /tests/test_templatetags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Unit tests for ``django-envelope`` template tags. 4 | """ 5 | 6 | from __future__ import unicode_literals 7 | 8 | from django.template import TemplateSyntaxError 9 | from django.template.loader import render_to_string 10 | from django.test import TestCase 11 | 12 | from envelope.forms import ContactForm 13 | from envelope.templatetags.envelope_tags import render_contact_form 14 | 15 | 16 | class RenderContactFormTestCase(TestCase): 17 | def test_no_form_in_context(self): 18 | """ 19 | {% render_contact_form %} raises a syntax error when there is no form 20 | in the context. 21 | """ 22 | context = {} 23 | with self.assertRaises(TemplateSyntaxError): 24 | render_contact_form(context) 25 | 26 | def test_antispamfield_not_escaped(self): 27 | """ 28 | Antispam fields should not be escaped by Django. 29 | """ 30 | context = {'form': ContactForm()} 31 | content = render_to_string('envelope/contact_form.html', context) 32 | self.assertNotIn('<div', content) 33 | -------------------------------------------------------------------------------- /tests/test_views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import unicode_literals, print_function 4 | 5 | """ 6 | Unit tests for ``django-envelope`` views. 7 | """ 8 | 9 | import unittest 10 | 11 | from django.conf import settings 12 | from django.contrib.auth.models import User 13 | 14 | try: 15 | from django.core.urlresolvers import reverse 16 | except ImportError: 17 | from django.urls import reverse 18 | 19 | from django.test import TestCase 20 | 21 | try: 22 | import honeypot 23 | except ImportError: 24 | honeypot = None 25 | 26 | from envelope import signals 27 | 28 | 29 | class ContactViewTestCase(TestCase): 30 | """ 31 | Unit tests for contact form view. 32 | """ 33 | 34 | def setUp(self): 35 | self.url = reverse('envelope-contact') 36 | self.customized_url = reverse('customized_class_contact') 37 | self.subclassed_url = reverse('subclassed_class_contact') 38 | self.honeypot = getattr(settings, 'HONEYPOT_FIELD_NAME', 'email2') 39 | self.form_data = { 40 | 'sender': 'zbyszek', 41 | 'email': 'test@example.com', 42 | 'subject': 'A subject', 43 | 'message': 'Hello there!', 44 | self.honeypot: '', 45 | } 46 | 47 | def test_response_data(self): 48 | """ 49 | A GET request displays the contact form. 50 | """ 51 | response = self.client.get(self.url) 52 | self.assertEqual(response.status_code, 200) 53 | self.assertTemplateUsed(response, "envelope/contact.html") 54 | form = response.context['form'] 55 | self.assertFalse(form.is_bound) 56 | 57 | def test_prefilled_form(self): 58 | """ 59 | When an authenticated user hits the form view, his username, full name 60 | and email address are automatically filled in. 61 | """ 62 | user = User.objects.create_user('test', 'test@example.org', 'password') 63 | user.first_name = 'John' 64 | user.last_name = 'Doe' 65 | user.save() 66 | logged_in = self.client.login(username='test', password='password') 67 | self.assertTrue(logged_in) 68 | response = self.client.get(self.url) 69 | self.assertContains(response, 'value="test (John Doe)"') 70 | self.assertContains(response, 'value="test@example.org"') 71 | 72 | self.client.logout() 73 | response = self.client.get(self.url) 74 | self.assertNotContains(response, 'value="test (John Doe)"') 75 | self.assertNotContains(response, 'value="test@example.org"') 76 | 77 | def test_prefilled_form_no_full_name(self): 78 | """ 79 | In case the user is authenticated, but doesn't have his first and last 80 | name set (depends on the registration process), only his username is 81 | prefilled in the "From" field. 82 | """ 83 | User.objects.create_user('test', 'test@example.org', 'password') 84 | logged_in = self.client.login(username='test', password='password') 85 | self.assertTrue(logged_in) 86 | response = self.client.get(self.url) 87 | self.assertContains(response, 'value="test"') 88 | 89 | @unittest.skipIf(honeypot is None, "django-honeypot is not installed") 90 | def test_honeypot(self): 91 | """ 92 | If the honeypot field is not empty, keep the spammer off the page. 93 | """ 94 | self.form_data.update({self.honeypot: 'some value'}) 95 | response = self.client.post(self.url, self.form_data) 96 | self.assertEqual(response.status_code, 400) 97 | self.form_data.update({self.honeypot: ''}) 98 | response = self.client.post(self.url, self.form_data, follow=True) 99 | self.assertEqual(response.status_code, 200) 100 | 101 | def test_form_invalid(self): 102 | """ 103 | If the POST data is incorrect, the form is invalid. 104 | """ 105 | self.form_data.update({'sender': ''}) 106 | response = self.client.post(self.url, self.form_data) 107 | self.assertEqual(response.status_code, 200) 108 | 109 | def test_form_successful(self): 110 | """ 111 | If the data is correct, a message is sent and the user is redirected. 112 | """ 113 | response = self.client.post(self.url, self.form_data, follow=True) 114 | self.assertRedirects(response, self.url) 115 | self.assertEqual(len(response.redirect_chain), 1) 116 | 117 | def test_signal_before_send(self): 118 | """ 119 | A ``before_send`` signal is emitted before sending the message. 120 | """ 121 | # ugly trick to access the variable from inner scope 122 | params = {} 123 | 124 | def handle_before_send(sender, request, form, **kwargs): 125 | params['form'] = form 126 | 127 | signals.before_send.connect(handle_before_send) 128 | self.client.post(self.url, self.form_data, follow=True) 129 | self.assertEqual(params['form'].cleaned_data['email'], self.form_data['email']) 130 | 131 | def test_signal_after_send(self): 132 | """ 133 | An ``after_send`` signal is sent after succesfully sending the message. 134 | """ 135 | params = {} 136 | 137 | def handle_after_send(sender, message, form, **kwargs): 138 | params['message'] = message 139 | 140 | signals.after_send.connect(handle_after_send) 141 | self.client.post(self.url, self.form_data, follow=True) 142 | self.assertIn(self.form_data['subject'], params['message'].subject) 143 | 144 | def test_custom_template(self): 145 | """ 146 | You can change the default template used to render the form. 147 | """ 148 | response = self.client.get(self.customized_url) 149 | self.assertTemplateUsed(response, "customized_contact.html") 150 | 151 | def test_custom_success_url(self): 152 | """ 153 | The view redirects to a custom success_url when the form is valid. 154 | """ 155 | response = self.client.post(self.customized_url, self.form_data) 156 | self.assertRedirects(response, self.customized_url) 157 | 158 | def test_issue_18(self): 159 | """ 160 | ContactView subclasses should also trigger spam filtering. 161 | 162 | See: https://github.com/zsiciarz/django-envelope/issues/18 163 | """ 164 | self.form_data.update({self.honeypot: 'some value'}) 165 | response = self.client.post(self.subclassed_url, self.form_data, follow=True) 166 | self.assertEqual(response.status_code, 400) 167 | -------------------------------------------------------------------------------- /tests/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | 3 | from envelope.views import ContactView 4 | 5 | 6 | class SubclassedContactView(ContactView): 7 | pass 8 | 9 | 10 | urlpatterns = [ 11 | url(r'', include('envelope.urls')), 12 | url(r'^class_contact/', ContactView.as_view(), name='class_contact'), 13 | 14 | url(r'^customized_class_contact/', 15 | ContactView.as_view( 16 | success_url='customized_class_contact', 17 | template_name='customized_contact.html' 18 | ), 19 | name='customized_class_contact' 20 | ), 21 | 22 | url(r'^subclassed_class_contact/', 23 | SubclassedContactView.as_view(), 24 | name='subclassed_class_contact' 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | {py27,py35,py36}-django111, 4 | {py35,py36,py37,py38}-django22, 5 | {py36,py37,py38}-django{30,master} 6 | 7 | [testenv] 8 | deps= 9 | django111: Django>=1.11,<2.0 10 | django22: Django>=2.2,<3.0 11 | django30: Django>=3.0,<4.0 12 | djangomaster: https://github.com/django/django/archive/master.tar.gz 13 | py{35,36,37,38}: django-honeypot 14 | py27: django-honeypot<0.8 15 | py27: mock==3.0.5 16 | coverage 17 | 18 | commands= coverage run ./runtests.py 19 | 20 | [travis:env] 21 | DJANGO = 22 | 1.11: django111 23 | 2.2: django22 24 | 3.0: django30 25 | master: djangomaster --------------------------------------------------------------------------------