pax_global_header00006660000000000000000000000064142453257610014523gustar00rootroot0000000000000052 comment=2c50ed6c99b645fe0b80f221a3e62e40e90fed13 django-contact-form-2.0.1/000077500000000000000000000000001424532576100153575ustar00rootroot00000000000000django-contact-form-2.0.1/.github/000077500000000000000000000000001424532576100167175ustar00rootroot00000000000000django-contact-form-2.0.1/.github/workflows/000077500000000000000000000000001424532576100207545ustar00rootroot00000000000000django-contact-form-2.0.1/.github/workflows/ci.yml000066400000000000000000000013461424532576100220760ustar00rootroot00000000000000name: CI on: [push] env: PYTHON_AKISMET_API_KEY: ${{ secrets.PYTHON_AKISMET_API_KEY }} PYTHON_AKISMET_BLOG_URL: ${{ secrets.PYTHON_AKISMET_BLOG_URL }} jobs: tests: name: tox on ${{ matrix.python-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: "Install dependencies" run: | python -VV python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade virtualenv tox tox-gh-actions - run: "python -m tox" django-contact-form-2.0.1/.gitignore000066400000000000000000000001501424532576100173430ustar00rootroot00000000000000*.pyc __pycache__ *.egg-info docs/_build/ dist/ .coverage .python-version .tox/ *.sh build/ .mypy_cache django-contact-form-2.0.1/LICENSE000066400000000000000000000027631424532576100163740ustar00rootroot00000000000000Copyright (c) 2007-2022, James Bennett All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. django-contact-form-2.0.1/MANIFEST.in000066400000000000000000000002071424532576100171140ustar00rootroot00000000000000include LICENSE include MANIFEST.in include AUTHORS recursive-include docs * include tox.ini include runtests.py graft src graft tests django-contact-form-2.0.1/README.rst000066400000000000000000000007361424532576100170540ustar00rootroot00000000000000.. -*-restructuredtext-*- .. image:: https://github.com/ubernostrum/django-contact-form/workflows/CI/badge.svg :alt: CI status image :target: https://github.com/ubernostrum/django-contact-form/actions?query=workflow%3ACI This application provides extensible contact-form functionality for `Django `_ sites. Full documentation for all functionality is included and is also `available online `_.django-contact-form-2.0.1/docs/000077500000000000000000000000001424532576100163075ustar00rootroot00000000000000django-contact-form-2.0.1/docs/Makefile000066400000000000000000000127601424532576100177550ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-contact-form.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-contact-form.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/django-contact-form" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-contact-form" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." django-contact-form-2.0.1/docs/conf.py000066400000000000000000000024161424532576100176110ustar00rootroot00000000000000import os import sys on_rtd = os.environ.get("READTHEDOCS", None) == "True" extensions = ["sphinx.ext.intersphinx"] templates_path = ["_templates"] source_suffix = ".rst" master_doc = "index" project = "django-contact-form" copyright = "2007-2022, James Bennett" version = "2.0" release = "2.0.1" exclude_trees = ["_build"] pygments_style = "sphinx" htmlhelp_basename = "django-contact-formdoc" latex_documents = [ ( "index", "django-contact-form.tex", "django-contact-form Documentation", "James Bennett", "manual", ), ] intersphinx_mapping = { "django": ( "https://docs.djangoproject.com/en/stable/", "https://docs.djangoproject.com/en/stable/_objects/", ), "python": ("https://docs.python.org/3", None), } if not on_rtd: import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Spelling check needs an additional module that is not installed by default. # Add it only if spelling check is requested so docs can be generated without it. if "spelling" in sys.argv: extensions.append("sphinxcontrib.spelling") # Spelling language. spelling_lang = "en_US" # Location of word list. spelling_word_list_filename = "spelling_wordlist.txt" django-contact-form-2.0.1/docs/faq.rst000066400000000000000000000117261424532576100176170ustar00rootroot00000000000000.. _faq: Frequently asked questions ========================== The following notes answer some common questions, and may be useful to you when installing, configuring or using django-contact-form. What versions of Django and Python are supported? ------------------------------------------------- As of django-contact-form |release|, Django 3.2 and 4.0 are supported, on Python 3.7 (Django 3.2 only), 3.8, 3.9, and 3.10. Note that Django 3.2's support for Python 3.10 was added in Django 3.2.9, so you may experience issues with Python 3.10 and earlier Django 3.2 versions. What license is django-contact-form under? ---------------------------------------------- django-contact-form is offered under a three-clause BSD-style license; this is `an OSI-approved open-source license `_, and allows you a large degree of freedom in modifying and redistributing the code. For the full terms, see the file `LICENSE` which came with your copy of django-contact-form; if you did not receive a copy of this file, you can view it online at . Why aren't there any default templates I can use? ------------------------------------------------- Usable default templates, for an application designed to be widely reused, are essentially impossible to produce; variations in site design, block structure, etc. cannot be reliably accounted for. As such, django-contact-form provides bare-bones (i.e., containing no HTML structure whatsoever) templates in its source distribution to enable running tests, and otherwise just provides good documentation of all required templates and the context made available to them. Why am I getting a bunch of `BadHeaderError` exceptions? ---------------------------------------------------------- Most likely, you have an error in your :class:`~django_contact_form.forms.ContactForm` subclass. Specifically, one or more of :attr:`~django_contact_form.forms.ContactForm.from_email`, :attr:`~django_contact_form.forms.ContactForm.recipient_list` or :meth:`~django_contact_form.forms.ContactForm.subject` are returning values which contain newlines. As a security precaution against `email header injection attacks `_ (which allow spammers and other malicious users to manipulate email and potentially cause automated systems to send mail to unintended recipients), `Django's email-sending framework does not permit newlines in message headers `_. :exc:`~django.core.mail.BadHeaderError` is the exception Django raises when a newline is detected in a header. By default, :meth:`~django_contact_form.forms.ContactForm.subject` will forcibly condense the subject to a single line. Note that this only applies to the headers of an email message; the message body can (and usually does) contain newlines. I found a bug or want to make an improvement! --------------------------------------------- The canonical development repository for django-contact-form is online at . Issues and pull requests can both be filed there. If you'd like to contribute to django-contact-form, that's great! Just please remember that pull requests should include tests and documentation for any changes made, and that following `PEP 8 `_ is mandatory. Pull requests without documentation won't be merged, and PEP 8 style violations or test coverage below 100% are both configured to break the build. I'm getting errors about "akismet" when trying to run tests? ------------------------------------------------------------ The full test suite of django-contact-form exercises all of its functionality, including the spam-filtering :class:`~django_contact_forms.forms.AkismetContactForm`. That class uses `the Wordpress Akismet spam-detection service `_ to perform spam filtering, and so requires the Python `akismet` module to communicate with the Akismet service, and some additional configuration (in the form of a valid Akismet API key and associated URL). By default, the tests for :class:`~django_contact_forms.forms.AkismetContactForm` will be skipped unless the required configuration (in the form of either a pair of Django settings, or a pair of environment variables) is detected. However, if you have supplied Akismet configuration but do *not* have the Python `akismet` module, you will see test errors from attempts to import `akismet`. You can resolve this by running:: pip install akismet or (if you do not intend to use :class:`~django_contact_forms.forms.AkismetContactForm`) by no longer configuring the Django settings/environment variables used by Akismet. Additionally, if the :class:`~django_contact_forms.forms.AkismetContactForm` tests are skipped, the default code-coverage report will fail due to the relevant code not being exercised during the test run.django-contact-form-2.0.1/docs/forms.rst000066400000000000000000000217711424532576100201770ustar00rootroot00000000000000.. _forms: .. module:: django_contact_form.forms Contact form classes ==================== There are two contact-form classes included in django-contact-form; one provides all the infrastructure for a contact form, and will usually be the base class for subclasses which want to extend or modify functionality. The other is a subclass which adds spam filtering to the contact form. The ContactForm class --------------------- .. class:: ContactForm The base contact form class from which all contact form classes should inherit. If you don't need any customization, you can use this form to provide basic contact-form functionality; it will collect name, email address and message. The :class:`~django_contact_form.views.ContactFormView` included in this application knows how to work with this form and can handle many types of subclasses as well (see below for a discussion of the important points), so in many cases it will be all that you need. If you'd like to use this form or a subclass of it from one of your own views, here's how: 1. When you instantiate the form, pass the current :class:`~django.http.HttpRequest` object as the keyword argument `request`; this is used internally by the base implementation, and also made available so that subclasses can add functionality which relies on inspecting the request (such as spam filtering). 2. To send the message, call the form's :meth:`save` method, which accepts the keyword argument `fail_silently` and defaults it to `False`. This argument is passed directly to Django's :func:`~django.core.mail.send_mail` function, and allows you to suppress or raise exceptions as needed for debugging. The :meth:`save` method has no return value. Other than that, treat it like any other form; validity checks and validated data are handled normally, through the :meth:`~django.forms.Form.is_valid` method and the :attr:`~django.forms.Form.cleaned_data` dictionary. Under the hood, this form uses a somewhat abstracted interface in order to make it easier to subclass and add functionality. The following attributes play a role in determining behavior, and any of them can be implemented as an attribute or as a method (for example, if you wish to have :attr:`from_email` be dynamic, you can implement a method named :meth:`from_email` instead of setting the attribute :attr:`from_email`). .. attribute:: from_email The email address (:class:`str`) to use in the `From:` header of the message. By default, this is the value of the Django setting :data:`~django.conf.settings.DEFAULT_FROM_EMAIL`. .. attribute:: recipient_list A :class:`list` of recipients for the message. By default, this is the email addresses specified in the setting :data:`~django.conf.settings.MANAGERS`. .. attribute:: subject_template_name A :class:`str`, the name of the template to use when rendering the subject line of the message. By default, this is `django_contact_form/contact_form_subject.txt`. .. attribute:: template_name A :class:`str`, the name of the template to use when rendering the body of the message. By default, this is `django_contact_form/contact_form.txt`. And two methods are involved in producing the contents of the message to send: .. method:: message Returns the body of the message to send. By default, this is accomplished by rendering the template name specified in :attr:`template_name`. :rtype: str .. method:: subject Returns the subject line of the message to send. By default, this is accomplished by rendering the template name specified in :attr:`subject_template_name`. :rtype: str .. warning:: **Subject must be a single line** The subject of an email is sent in a header (named `Subject:`). Because email uses newlines as a separator between headers, newlines in the subject can cause it to be interpreted as multiple headers; this is the `header injection attack `_. To prevent this, :meth:`subject` will always force the subject to a single line of text, stripping all newline characters. If you override :meth:`subject`, be sure to either do this manually, or use :func:`super` to call the parent implementation. Finally, the message itself is generated by the following two methods: .. method:: get_message_dict This method loops through :attr:`from_email`, :attr:`recipient_list`, :meth:`message` and :meth:`subject`, collecting those parts into a dictionary with keys corresponding to the arguments to Django's `send_mail` function, then returns the dictionary. Overriding this allows essentially unlimited customization of how the message is generated. Note that for compatibility, implementations which override this should support callables for the values of :attr:`from_email` and :attr:`recipient_list`. :rtype: dict .. method:: get_message_context .. warning:: **Renamed method** Prior to django-contact-form 2.x, this method was named `get_context()`. It was renamed to `get_message_context()` in django-contact-form 2.0. See :ref:`the upgrade guide ` for details. For methods which render portions of the message using templates (by default, :meth:`message` and :meth:`subject`), generates the context used by those templates. The default context will be a :class:`~django.template.RequestContext` (using the current HTTP request, so user information is available), plus the contents of the form's :attr:`~django.forms.Form.cleaned_data` dictionary, and one additional variable: `site` If `django.contrib.sites` is installed, the currently-active :class:`~django.contrib.sites.models.Site` object. Otherwise, a :class:`~django.contrib.sites.requests.RequestSite` object generated from the request. :rtype: dict Meanwhile, the following attributes/methods generally should not be overridden; doing so may interfere with functionality, may not accomplish what you want, and generally any desired customization can be accomplished in a more straightforward way through overriding one of the attributes/methods listed above. .. attribute:: request The :class:`~django.http.HttpRequest` object representing the current request. This is set automatically in `__init__()`, and is used both to generate a :class:`~django.template.RequestContext` for the templates and to allow subclasses to engage in request-specific behavior. .. method:: save If the form has data and is valid, will send the email, by calling :meth:`get_message_dict` and passing the result to Django's :func:`~django.core.mail.send_mail` function. Note that subclasses which override `__init__` or :meth:`save` need to accept `*args` and `**kwargs`, and pass them via :func:`super`, in order to preserve behavior (each of those methods accepts at least one additional argument, and this application expects and requires them to do so). The Akismet (spam-filtering) contact form class ----------------------------------------------- .. class:: AkismetContactForm A subclass of :class:`ContactForm` which adds spam filtering, via `the Wordpress Akismet spam-detection service `_. Use of this class requires you to provide configuration for the Akismet web service; you'll need to obtain an Akismet API key, and you'll need to associate it with the site you'll use the contact form on. You can do this at . Once you have, you can configure in either of two ways: 1. Put your Akismet API key in the Django setting :data:`~django.conf.settings.AKISMET_API_KEY`, and the URL it's associated with in the setting :class:`~django.conf.settings.AKISMET_BLOG_URL`, or 2. Put your Akismet API key in the environment variable `PYTHON_AKISMET_API_KEY`, and the URL it's associated with in the environment variable `PYTHON_AKISMET_BLOG_URL`. You will also need `the Python Akismet module `_ to communicate with the Akismet web service. You can install it by running `pip install akismet`, or django-contact-form can install it automatically for you if you run `pip install django-contact-form[akismet]`. Once you have an Akismet API key and URL configured, and the `akismet` module installed, you can drop in :class:`AkismetContactForm` anywhere you would have used :class:`ContactForm`. A URLconf is provided in django-contact-form, at `django_contact_form.akismet_urls`, which will correctly configure :class:`AkismetContactForm` for you. django-contact-form-2.0.1/docs/index.rst000066400000000000000000000015431424532576100201530ustar00rootroot00000000000000.. _index: django-contact-form |release| ============================= django-contact-form provides customizable contact-form functionality for `Django `_-powered Web sites. Basic functionality (collecting a name, email address and message) can be achieved out of the box by setting up a few templates and adding one line to your site's root URLconf: .. code-block:: python path('contact/', include('django_contact_form.urls')), For notes on getting started quickly, and on how to customize django-contact-form's behavior, read through the full documentation below. .. toctree:: :caption: Installation and configuration :maxdepth: 1 install quickstart .. toctree:: :caption: For developers :maxdepth: 1 forms views .. toctree:: :caption: Other documentation :maxdepth: 1 upgrade faqdjango-contact-form-2.0.1/docs/install.rst000066400000000000000000000041111424532576100205040ustar00rootroot00000000000000.. _install: Installation guide ================== The |release| release of django-contact-form supports Django 3.2 and 4.0 on Python 3.7 (Django 3.2 only), 3.8, 3.9, and 3.10. Note that Django 3.2's support for Python 3.10 was added in Django 3.2.9, so you may experience issues with Python 3.10 and earlier Django 3.2 versions. Normal installation ------------------- The preferred method of installing django-contact-form is via `pip`, the standard Python package-installation tool. If you don't have `pip`, instructions are available for `how to obtain and install it `_. If you're using a supported version of Python, `pip` should have come bundled with your installation of Python. Once you have `pip`, type:: pip install django-contact-form If you plan to use the included spam-filtering contact form class, :class:`~contact_form.forms.AkismetContactForm`, you will also need `the Python akismet module `_. You can manually install it via `pip install akismet`, or tell django-contact-form to install it for you, by running:: pip install django-contact-form[akismet] If you don't have a copy of a compatible version of Django, installing django-contact-form will also automatically install one for you. Installing from a source checkout --------------------------------- If you want to work on django-contact-form, you can obtain a source checkout. The development repository for django-contact-form is at . If you have `git `_ installed, you can obtain a copy of the repository by typing:: git clone https://github.com/ubernostrum/django-contact-form.git From there, you can use git commands to check out the specific revision you want, and perform an "editable" install (allowing you to change code as you work on it) by typing:: pip install -e . Next steps ---------- To get up and running quickly, check out :ref:`the quick start guide `. For full documentation, see :ref:`the documentation index `.django-contact-form-2.0.1/docs/make.bat000066400000000000000000000120021424532576100177070ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-contact-form.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-contact-form.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end django-contact-form-2.0.1/docs/quickstart.rst000066400000000000000000000136241424532576100212410ustar00rootroot00000000000000.. _quickstart: Quick start guide ================= First you'll need to have Django and django-contact-form installed; for details on that, see :ref:`the installation guide `. Once that's done, you can start setting up django-contact-form. First, add `'django_contact_form'` to your :data:`~django.conf.settings.INSTALLED_APPS` setting. Then, you can begin configuring. URL configuration ----------------- The quickest way to set up the views in django-contact-form is to use the provided URLconf, found at `django_contact_form.urls`. You can include it wherever you like in your site's URL configuration; for example, to have it live at the URL `/contact/`: .. code-block:: python from django.urls import include, path urlpatterns = [ # ... other URL patterns for your site ... path('contact/', include('django_contact_form.urls')), ] If you'll be using a custom form class, you'll need to manually set up your URLs so you can tell django-contact-form about your form class. For example: .. code-block:: python from django.urls import include, path from django.views.generic import TemplateView from django_contact_form.views import ContactFormView from yourapp.forms import YourCustomFormClass urlpatterns = [ # ... other URL patterns for your site ... path('contact/', ContactFormView.as_view( form_class=YourCustomFormClass ), name='django_contact_form'), path('contact/sent/', TemplateView.as_view( template_name='django_contact_form/contact_form_sent.html' ), name='django_contact_form_sent'), ] .. important:: **Where to put custom forms and views** When writing a custom form class (or custom :class:`~django_contact_form.views.ContactFormView` subclass), **don't** put your custom code inside django-contact-form. Instead, put your custom code in the appropriate place (a `forms.py` or `views.py` file) in an application you've written. Required templates ------------------ The two views above will need several templates to be created. `django_contact_form/contact_form.html` ``````````````````````````````````````` This is used to display the contact form. It has a :class:`~django.template.RequestContext` (so any context processors will be applied), and also provides the form instance as the context variable `form`. `django_contact_form/contact_form_sent.html` ```````````````````````````````````````````` This is used after a successful form submission, to let the user know their message has been sent. It has a :class:`~django.template.RequestContext`, but provides no additional context variables of its own. `django_contact_form/contact_form.txt` `````````````````````````````````````` Used to render the subject of the email. Will receive a :class:`~django.template.RequestContext` with the following additional variables: `body` The message the user typed. `email` The email address the user supplied. `name` The name the user supplied. `site` The current site. Either a :class:`~django.contrib.sites.models.Site` or :class:`~django.contrib.sites.requests.RequestSite` instance, depending on whether `Django's sites framework `_ is installed). `django_contact_form/contact_form_subject.txt` `````````````````````````````````````````````` Used to render the subject of the email. Will receive a :class:`~django.template.RequestContext` with the following additional variables: `body` The message the user typed. `email` The email address the user supplied. `name` The name the user supplied. `site` The current site. Either a :class:`~django.contrib.sites.models.Site` or :class:`~django.contrib.sites.requests.RequestSite` instance, depending on whether `Django's sites framework `_ is installed). .. warning:: **Subject must be a single line** In order to prevent `header injection attacks `_, the subject *must* be only a single line of text, and Django's email framework will reject any attempt to send an email with a multi-line subject. So it's a good idea to ensure your `contact_form_subject.txt` template only produces a single line of output when rendered; as a precaution, however, django-contact-form will, by default, condense the output of this template to a single line. Using a spam-filtering contact form ----------------------------------- Spam filtering is a common desire for contact forms, due to the large amount of spam they can attract. There is a spam-filtering contact form class included in django-contact-form: :class:`~django_contact_form.forms.AkismetContactForm`, which uses `the Wordpress Akismet spam-detection service `_. To use this form, you will need to do the following things: 1. Install the Python `akismet` module to allow django-contact-form to communicate with the Akismet service. You can do this via `pip install akismet`, or as you install django-contact-form via `pip install django-contact-form[akismet]`. 2. Obtain an Akismet API key from , and associate it with the URL of your site. 3. Supply the API key and URL for django-contact-form to use. You can either place them in the Django settings :data:`~django.conf.settings.AKISMET_API_KEY` and :data:`~django.conf.settings.AKISMET_BLOG_URL`, or in the environment variables `PYTHON_AKISMET_API_KEY` and `PYTHON_AKISMET_BLOG_URL`. Then you can replace the suggested URLconf above with the following: .. code-block:: python from django.urls import include, path urlpatterns = [ # ... other URL patterns for your site ... path('contact/', include('django_contact_form.akismet_urls')), ] django-contact-form-2.0.1/docs/spelling_wordlist.txt000066400000000000000000000001531424532576100226130ustar00rootroot00000000000000Akismet akismet callables config customizable dev django multi online spam spammers subclasses subclassing django-contact-form-2.0.1/docs/upgrade.rst000066400000000000000000000044171424532576100204760ustar00rootroot00000000000000.. _upgrade: Upgrading from previous versions ================================ The current release series of django-contact-form is the 2.x series, which is not backwards-compatible with the django-contact-form 1.x release series. Changes between django-contact-form 1.x and 2.x ----------------------------------------------- Module renaming ~~~~~~~~~~~~~~~ Prior to 2.x, django-contact-form installed a Python module named `contact_form`. To avoid silent incompatibilities, and to conform to more recent best practices, django-contact-form 2.x now installs a module named `django_contact_form`. Attempts to import from the `contact_form` module will immediately fail with :exc:`ImportError`. Many installations will be able to adapt by replacing references to `contact_form` with references to `django_contact_form`. Template directory renamed ~~~~~~~~~~~~~~~~~~~~~~~~~~ Similar to the module renaming above, the name of the default directory in which django-contact-form looks for templates has changed from `contact_form/` to `django_contact_form/`. .. _renamed-get-context: Method renamed: get_context() -> get_message_context() `````````````````````````````````````````````````````` Prior to 2.x, :class:`~django_contact_form.forms.ContactForm` provided a method named `get_context()` which was used to generate the template context from which the message would be rendered. However, Django 4.0 introduced `a new template-based system for rendering forms `_, and as a result :class:`django.forms.Form` now has a method named :meth:`~django.forms.Form.get_context`. To resolve this conflict with Django's own base form class, the method in django-contact-form has been renamed to :meth:`~django_contact_form.forms.ContactForm.get_message_context`, which hopefully will not be adopted by any future version of Django's own forms system. If you were previously overriding `get_context()`, you should rename your overridden method to :meth:`~django_contact_form.forms.ContactForm.get_message_context` to ensure it is still called properly. If you have other code which called `get_context()`, you should update any such references to call :meth:`~django_contact_form.forms.ContactForm.get_message_context` instead.django-contact-form-2.0.1/docs/views.rst000066400000000000000000000061771424532576100202110ustar00rootroot00000000000000.. _views: .. module:: django_contact_form.views Built-in views ============== .. class:: ContactFormView The base view class from which most custom contact-form views should inherit. If you don't need any custom functionality, and are content with the default :class:`~django_contact_form.forms.ContactForm` class, you can also use it as-is (and the provided URLConf, `django_contact_form.urls`, does exactly this). This is a subclass of Django's :class:`~django.views.generic.edit.FormView`, so refer to the Django documentation for a list of attributes/methods which can be overridden to customize behavior. One non-standard attribute is defined here: .. attribute:: recipient_list The list of email addresses to send mail to. If not specified, defaults to the :attr:`~django_contact_form.forms.ContactForm.recipient_list` of the form. Additionally, the following standard (from :class:`~django.views.generic.edit.FormView`) methods and attributes are commonly useful to override (all attributes below can also be passed to :meth:`~django.views.generic.base.View.as_view()` in the URLconf, permitting customization without the need to write a full custom subclass of :class:`ContactFormView`): .. attribute:: form_class The form class to use. By default, will be :class:`~django_contact_form.forms.ContactForm`. This can also be overridden as a method named :meth:`~django.views.generic.edit.FormMixin.form_class`; this permits, for example, per-request customization (by inspecting attributes of `self.request`). .. attribute:: template_name A :class:`str`, the template to use when rendering the form. By default, will be `django_contact_form/contact_form.html`. .. method:: get_success_url The URL to redirect to after successful form submission. Can be a hard-coded string, the string resulting from calling Django's :func:`~django.urls.reverse` helper, or the lazy object produced by Django's :func:`~django.urls.reverse_lazy` helper. Default value is the result of calling :func:`~django.urls.reverse_lazy` with the URL name `'django_contact_form_sent'`. :rtype: str .. method:: get_form_kwargs Returns additional keyword arguments (as a dictionary) to pass to the form class on initialization. By default, this will return a dictionary containing the current :class:`~django.http.HttpRequest` (as the key `request`) and, if :attr:`~ContactFormView.recipient_list` was defined, its value (as the key `recipient_list`). .. warning:: If you override :meth:`get_form_kwargs`, you **must** ensure that, at the very least, the keyword argument `request` is still provided, or :class:`~django_contact_form.forms.ContactForm` initialization will raise :exc:`TypeError`. The easiest approach is to use :func:`super` to call the base implementation in :class:`ContactFormView`, and modify the dictionary it returns. :rtype: dict django-contact-form-2.0.1/runtests.py000066400000000000000000000050541424532576100176240ustar00rootroot00000000000000""" A standalone test runner script, configuring the minimum settings required for tests to execute. Re-use at your own risk: many Django applications will require full settings and/or templates in order to execute their tests. """ import os import sys from django.utils.crypto import get_random_string APP_DIR = os.path.abspath(os.path.dirname(__file__)) # Minimum settings required for django-contact-form to work. SETTINGS_DICT = { "BASE_DIR": APP_DIR, "INSTALLED_APPS": ( "django_contact_form", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sites", ), "ROOT_URLCONF": "django_contact_form.urls", "DATABASES": { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(APP_DIR, "db.sqlite3"), } }, "MIDDLEWARE": ( "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", ), "SITE_ID": 1, "DEFAULT_FROM_EMAIL": "contact@example.com", "MANAGERS": [("Manager", "noreply@example.com")], "SECRET_KEY": get_random_string(12), "TEMPLATES": [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [os.path.join(APP_DIR, "tests/templates")], "OPTIONS": { "context_processors": [ "django.contrib.auth.context_processors.auth", "django.template.context_processors.debug", "django.template.context_processors.i18n", "django.template.context_processors.media", "django.template.context_processors.static", "django.template.context_processors.tz", "django.contrib.messages.context_processors.messages", ] }, } ], } def run_tests(): # Making Django run this way is a two-step process. First, call # settings.configure() to give Django settings to work with: from django.conf import settings settings.configure(**SETTINGS_DICT) # Then, call django.setup() to initialize the application cache # and other bits: import django django.setup() # Now we instantiate a test runner... from django.test.utils import get_runner TestRunner = get_runner(settings) # And then we run tests and return the results. test_runner = TestRunner(verbosity=2, interactive=True) failures = test_runner.run_tests(["tests"]) sys.exit(bool(failures)) if __name__ == "__main__": run_tests() django-contact-form-2.0.1/setup.cfg000066400000000000000000000005461424532576100172050ustar00rootroot00000000000000[coverage:report] fail_under = 100 exclude_lines = pragma: no cover def __str__ raise NotImplementedError [flake8] extend-ignore = E203 max-complexity = 10 max-line-length = 88 [isort] known_first_party = django_contact_form known_third_party = django profile = black [check-manifest] ignore = __pycache__ *.pyc ignore-bad-ideas = *.mo django-contact-form-2.0.1/setup.py000066400000000000000000000025501424532576100170730ustar00rootroot00000000000000import os from setuptools import find_packages, setup setup( name="django-contact-form", version="2.0.1", zip_safe=False, # eggs are the devil. description="A generic contact-form application for Django", long_description=open(os.path.join(os.path.dirname(__file__), "README.rst")).read(), author="James Bennett", author_email="james@b-list.org", url="https://github.com/ubernostrum/django-contact-form/", include_package_data=True, package_dir={"": "src"}, packages=find_packages("src"), classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Framework :: Django", "Framework :: Django :: 3.2", "Framework :: Django :: 4.0", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Topic :: Utilities", ], keywords=["django", "email", "contact-form"], python_requires=">=3.7", install_requires=["Django>=3.2"], extras_require={"akismet": ["akismet"]}, ) django-contact-form-2.0.1/src/000077500000000000000000000000001424532576100161465ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/000077500000000000000000000000001424532576100221465ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/__init__.py000066400000000000000000000000001424532576100242450ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/akismet_urls.py000066400000000000000000000013041424532576100252200ustar00rootroot00000000000000""" Example URLConf for a contact form with Akismet spam filtering. If all you want is the basic contact-form plus spam filtering, include this URLConf somewhere in your URL hierarchy (for example, at ``/contact/``). """ from django.urls import path from django.views.generic import TemplateView from .forms import AkismetContactForm from .views import ContactFormView urlpatterns = [ path( "", ContactFormView.as_view(form_class=AkismetContactForm), name="django_contact_form", ), path( "sent/", TemplateView.as_view( template_name="django_contact_form/contact_form_sent.html" ), name="django_contact_form_sent", ), ] django-contact-form-2.0.1/src/django_contact_form/forms.py000066400000000000000000000120141424532576100236440ustar00rootroot00000000000000""" A base contact form for allowing users to send email messages through a web interface. """ from django import forms from django.conf import settings from django.contrib.sites.shortcuts import get_current_site from django.core.mail import send_mail from django.template import loader from django.utils.translation import gettext_lazy as _ class ContactForm(forms.Form): """ The base contact form class from which all contact form classes should inherit. """ name = forms.CharField(max_length=100, label=_("Your name")) email = forms.EmailField(max_length=200, label=_("Your email address")) body = forms.CharField(widget=forms.Textarea, label=_("Your message")) from_email = settings.DEFAULT_FROM_EMAIL recipient_list = [mail_tuple[1] for mail_tuple in settings.MANAGERS] subject_template_name = "django_contact_form/contact_form_subject.txt" template_name = "django_contact_form/contact_form.txt" def __init__( self, data=None, files=None, request=None, recipient_list=None, *args, **kwargs ): if request is None: raise TypeError("Keyword argument 'request' must be supplied") self.request = request if recipient_list is not None: self.recipient_list = recipient_list super().__init__(data=data, files=files, *args, **kwargs) def message(self): """ Render the body of the message to a string. """ template_name = ( self.template_name() if callable(self.template_name) else self.template_name ) return loader.render_to_string( template_name, self.get_message_context(), request=self.request ) def subject(self): """ Render the subject of the message to a string. """ template_name = ( self.subject_template_name() if callable(self.subject_template_name) else self.subject_template_name ) subject = loader.render_to_string( template_name, self.get_message_context(), request=self.request ) return "".join(subject.splitlines()) def get_message_context(self): """ Return the context used to render the templates for the email subject and body. By default, this context includes: * All of the validated values in the form, as variables of the same names as their fields. * The current ``Site`` object, as the variable ``site``. * Any additional variables added by context processors (this will be a ``RequestContext``). """ if not self.is_valid(): raise ValueError("Cannot generate Context from invalid contact form") return dict(self.cleaned_data, site=get_current_site(self.request)) def get_message_dict(self): """ Generate the various parts of the message and return them in a dictionary, suitable for passing directly as keyword arguments to ``django.core.mail.send_mail()``. By default, the following values are returned: * ``from_email`` * ``message`` * ``recipient_list`` * ``subject`` """ if not self.is_valid(): raise ValueError("Message cannot be sent from invalid contact form") message_dict = {} for message_part in ("from_email", "message", "recipient_list", "subject"): attr = getattr(self, message_part) message_dict[message_part] = attr() if callable(attr) else attr return message_dict def save(self, fail_silently=False): """ Build and send the email message. """ send_mail(fail_silently=fail_silently, **self.get_message_dict()) class AkismetContactForm(ContactForm): """ Contact form which doesn't add any extra fields, but does add an Akismet spam check to the validation routine. Requires the Python Akismet library, and two configuration parameters: an Akismet API key and the URL the key is associated with. These can be supplied either as the settings AKISMET_API_KEY and AKISMET_BLOG_URL, or the environment variables PYTHON_AKISMET_API_KEY and PYTHON_AKISMET_BLOG_URL. """ SPAM_MESSAGE = _("Your message was classified as spam.") def clean_body(self): from akismet import Akismet akismet_api = Akismet( key=getattr(settings, "AKISMET_API_KEY", None), blog_url=getattr(settings, "AKISMET_BLOG_URL", None), ) akismet_kwargs = { "user_ip": self.request.META["REMOTE_ADDR"], "user_agent": self.request.META.get("HTTP_USER_AGENT"), "comment_author": self.cleaned_data.get("name"), "comment_author_email": self.cleaned_data.get("email"), "comment_content": self.cleaned_data["body"], "comment_type": "contact-form", } if akismet_api.comment_check(**akismet_kwargs): raise forms.ValidationError(self.SPAM_MESSAGE) return self.cleaned_data["body"] django-contact-form-2.0.1/src/django_contact_form/locale/000077500000000000000000000000001424532576100234055ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/da/000077500000000000000000000000001424532576100237715ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/da/LC_MESSAGES/000077500000000000000000000000001424532576100255565ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/da/LC_MESSAGES/django.po000066400000000000000000000017731424532576100273700ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-01-23 18:03+0100\n" "PO-Revision-Date: 2020-01-23 18:00+0050\n" "Last-Translator: b'Anonymous User '\n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Translated-Using: django-rosetta 0.9.0\n" #: contact_form/forms.py:27 msgid "Your name" msgstr "Dit navn" #: contact_form/forms.py:28 msgid "Your email address" msgstr "Din emailadresse" #: contact_form/forms.py:29 msgid "Your message" msgstr "Din besked" #: contact_form/forms.py:148 msgid "Your message was classified as spam." msgstr "Din besked var klassifiseret som spam." django-contact-form-2.0.1/src/django_contact_form/locale/de/000077500000000000000000000000001424532576100237755ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/de/LC_MESSAGES/000077500000000000000000000000001424532576100255625ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/de/LC_MESSAGES/django.mo000066400000000000000000000010241424532576100273560ustar00rootroot00000000000000<\pq K Your email addressYour messageYour nameProject-Id-Version: Report-Msgid-Bugs-To: POT-Creation-Date: 2018-09-09 22:34-0700 PO-Revision-Date: 2016-09-20 08:30+0200 Last-Translator: Language-Team: Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.8.8 deine EmailadresseDeine NachrichtDein Namedjango-contact-form-2.0.1/src/django_contact_form/locale/de/LC_MESSAGES/django.po000066400000000000000000000015771424532576100273760ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-09-09 22:34-0700\n" "PO-Revision-Date: 2016-09-20 08:30+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.8.8\n" #: forms.py:21 msgid "Your name" msgstr "Dein Name" #: forms.py:23 msgid "Your email address" msgstr "Deine E-Mail-Adresse" #: forms.py:25 msgid "Your message" msgstr "Deine Nachricht" #: forms.py:140 msgid "Your message was classified as spam." msgstr "Deine Nachricht wurde als Spam klassifiziert." django-contact-form-2.0.1/src/django_contact_form/locale/en/000077500000000000000000000000001424532576100240075ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/en/LC_MESSAGES/000077500000000000000000000000001424532576100255745ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/en/LC_MESSAGES/django.mo000066400000000000000000000005721424532576100273770ustar00rootroot00000000000000$,8@9Project-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: POT-Creation-Date: 2018-09-09 22:34-0700 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit django-contact-form-2.0.1/src/django_contact_form/locale/en/LC_MESSAGES/django.po000066400000000000000000000014341424532576100274000ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-09-09 22:34-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:21 msgid "Your name" msgstr "" #: forms.py:23 msgid "Your email address" msgstr "" #: forms.py:25 msgid "Your message" msgstr "" #: forms.py:140 msgid "Your message was classified as spam." msgstr "" django-contact-form-2.0.1/src/django_contact_form/locale/es/000077500000000000000000000000001424532576100240145ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/es/LC_MESSAGES/000077500000000000000000000000001424532576100256015ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/es/LC_MESSAGES/django.mo000066400000000000000000000010601424532576100273750ustar00rootroot00000000000000<\pq v  &Your email addressYour messageYour nameProject-Id-Version: Report-Msgid-Bugs-To: POT-Creation-Date: 2018-09-09 22:34-0700 PO-Revision-Date: 2016-04-13 09:34+0200 Last-Translator: Urtzi Odriozola Language-Team: Language: es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.8.4 Tu emailTu mensajeTu nombredjango-contact-form-2.0.1/src/django_contact_form/locale/es/LC_MESSAGES/django.po000066400000000000000000000015731424532576100274110ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Urtzi Odriozola , 2016. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-09-09 22:34-0700\n" "PO-Revision-Date: 2016-04-13 09:34+0200\n" "Last-Translator: Urtzi Odriozola \n" "Language-Team: \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.8.4\n" #: forms.py:21 msgid "Your name" msgstr "Tu nombre" #: forms.py:23 msgid "Your email address" msgstr "Tu email" #: forms.py:25 msgid "Your message" msgstr "Tu mensaje" #: forms.py:140 msgid "Your message was classified as spam." msgstr "" django-contact-form-2.0.1/src/django_contact_form/locale/eu/000077500000000000000000000000001424532576100240165ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/eu/LC_MESSAGES/000077500000000000000000000000001424532576100256035ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/eu/LC_MESSAGES/django.mo000066400000000000000000000010641424532576100274030ustar00rootroot00000000000000<\pq v   )Your email addressYour messageYour nameProject-Id-Version: Report-Msgid-Bugs-To: POT-Creation-Date: 2018-09-09 22:34-0700 PO-Revision-Date: 2016-04-13 09:32+0200 Last-Translator: Urtzi Odriozola Language-Team: Language: eu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 1.8.4 Zure epostaZure mezuaZure izenadjango-contact-form-2.0.1/src/django_contact_form/locale/eu/LC_MESSAGES/django.po000066400000000000000000000015771424532576100274170ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Urtzi Odriozola , 2016. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-09-09 22:34-0700\n" "PO-Revision-Date: 2016-04-13 09:32+0200\n" "Last-Translator: Urtzi Odriozola \n" "Language-Team: \n" "Language: eu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.8.4\n" #: forms.py:21 msgid "Your name" msgstr "Zure izena" #: forms.py:23 msgid "Your email address" msgstr "Zure eposta" #: forms.py:25 msgid "Your message" msgstr "Zure mezua" #: forms.py:140 msgid "Your message was classified as spam." msgstr "" django-contact-form-2.0.1/src/django_contact_form/locale/fr/000077500000000000000000000000001424532576100240145ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/fr/LC_MESSAGES/000077500000000000000000000000001424532576100256015ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/fr/LC_MESSAGES/django.mo000066400000000000000000000010221424532576100273730ustar00rootroot00000000000000<\pq J  Your email addressYour messageYour nameProject-Id-Version: Report-Msgid-Bugs-To: POT-Creation-Date: 2018-09-09 22:34-0700 PO-Revision-Date: 2016-09-20 08:29+0200 Last-Translator: Language-Team: Language: fr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); X-Generator: Poedit 1.8.8 Votre adresse emailVotre messageVotre nomdjango-contact-form-2.0.1/src/django_contact_form/locale/fr/LC_MESSAGES/django.po000066400000000000000000000015671424532576100274140ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-09-09 22:34-0700\n" "PO-Revision-Date: 2016-09-20 08:29+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 1.8.8\n" #: forms.py:21 msgid "Your name" msgstr "Votre nom" #: forms.py:23 msgid "Your email address" msgstr "Votre adresse email" #: forms.py:25 msgid "Your message" msgstr "Votre message" #: forms.py:140 msgid "Your message was classified as spam." msgstr "Votre message a été classé comme spam." django-contact-form-2.0.1/src/django_contact_form/locale/it/000077500000000000000000000000001424532576100240215ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/it/LC_MESSAGES/000077500000000000000000000000001424532576100256065ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/it/LC_MESSAGES/django.po000066400000000000000000000013261424532576100274120ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-09-20 08:29+0200\n" "PO-Revision-Date: 2018-07-24 04:14+0200\n" "Last-Translator: GbP \n" "Language-Team: \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: forms.py:23 msgid "Your name" msgstr "Il tuo nome" #: forms.py:25 msgid "Your email address" msgstr "La tua email" #: forms.py:27 msgid "Your message" msgstr "Il tuo messaggio" django-contact-form-2.0.1/src/django_contact_form/locale/nl/000077500000000000000000000000001424532576100240165ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/nl/LC_MESSAGES/000077500000000000000000000000001424532576100256035ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/nl/LC_MESSAGES/django.mo000066400000000000000000000012131424532576100273770ustar00rootroot00000000000000Dl $ jC R%]Your email addressYour messageYour message was classified as spam.Your nameProject-Id-Version: PACKAGE VERSION Report-Msgid-Bugs-To: PO-Revision-Date: 2020-05-31 09:35+0200 Last-Translator: Jean-Paul Ladage Language-Team: Zest vertalers Language: nl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); Uw e-mailadresUw berichtUw bericht wordt als spam aangemerkt.Uw naamdjango-contact-form-2.0.1/src/django_contact_form/locale/nl/LC_MESSAGES/django.po000066400000000000000000000020011424532576100273760ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Jean-Paul Ladage , 2020. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-05-31 08:35+0200\n" "PO-Revision-Date: 2020-05-31 09:35+0200\n" "Last-Translator: Jean-Paul Ladage \n" "Language-Team: Zest vertalers \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: src/contact_form/forms.py:40 msgid "Your name" msgstr "Uw naam" #: src/contact_form/forms.py:41 msgid "Your email address" msgstr "Uw e-mailadres" #: src/contact_form/forms.py:42 msgid "Your message" msgstr "Uw bericht" #: src/contact_form/forms.py:161 msgid "Your message was classified as spam." msgstr "Uw bericht wordt als spam aangemerkt." django-contact-form-2.0.1/src/django_contact_form/locale/uk/000077500000000000000000000000001424532576100240245ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/uk/LC_MESSAGES/000077500000000000000000000000001424532576100256115ustar00rootroot00000000000000django-contact-form-2.0.1/src/django_contact_form/locale/uk/LC_MESSAGES/django.mo000066400000000000000000000012541424532576100274120ustar00rootroot00000000000000Dl $ *#$KHYour email addressYour messageYour message was classified as spam.Your nameProject-Id-Version: Report-Msgid-Bugs-To: POT-Creation-Date: 2018-09-09 22:34-0700 PO-Revision-Date: 2019-12-04 00:33+0200 Language: uk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Last-Translator: Language-Team: X-Generator: Poedit 2.2.4 Ваша електронна адресаТекст повідомленняВаше повідомлення класифіковано як спам.Як вас звати?django-contact-form-2.0.1/src/django_contact_form/locale/uk/LC_MESSAGES/django.po000066400000000000000000000016461424532576100274220ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-09-09 22:34-0700\n" "PO-Revision-Date: 2019-12-04 00:33+0200\n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Last-Translator: \n" "Language-Team: \n" "X-Generator: Poedit 2.2.4\n" #: forms.py:21 msgid "Your name" msgstr "Як вас звати?" #: forms.py:23 msgid "Your email address" msgstr "Ваша електронна адреса" #: forms.py:25 msgid "Your message" msgstr "Текст повідомлення" #: forms.py:140 msgid "Your message was classified as spam." msgstr "Ваше повідомлення класифіковано як спам." django-contact-form-2.0.1/src/django_contact_form/urls.py000066400000000000000000000011311424532576100235010ustar00rootroot00000000000000""" Example URLConf for a contact form. If all you want is the basic ContactForm with default behavior, include this URLConf somewhere in your URL hierarchy (for example, at ``/contact/``) """ from django.urls import path from django.views.generic import TemplateView from django_contact_form.views import ContactFormView urlpatterns = [ path("", ContactFormView.as_view(), name="django_contact_form"), path( "sent/", TemplateView.as_view( template_name="django_contact_form/contact_form_sent.html" ), name="django_contact_form_sent", ), ] django-contact-form-2.0.1/src/django_contact_form/views.py000066400000000000000000000016211424532576100236550ustar00rootroot00000000000000""" View which can render and send email from a contact form. """ from django.urls import reverse_lazy from django.views.generic.edit import FormView from .forms import ContactForm class ContactFormView(FormView): form_class = ContactForm recipient_list = None success_url = reverse_lazy("django_contact_form_sent") template_name = "django_contact_form/contact_form.html" def form_valid(self, form): form.save() return super().form_valid(form) def get_form_kwargs(self): # ContactForm instances require instantiation with an # HttpRequest. kwargs = super().get_form_kwargs() kwargs.update({"request": self.request}) # We may also have been given a recipient list when # instantiated. if self.recipient_list is not None: kwargs.update({"recipient_list": self.recipient_list}) return kwargs django-contact-form-2.0.1/tests/000077500000000000000000000000001424532576100165215ustar00rootroot00000000000000django-contact-form-2.0.1/tests/__init__.py000066400000000000000000000000001424532576100206200ustar00rootroot00000000000000django-contact-form-2.0.1/tests/templates/000077500000000000000000000000001424532576100205175ustar00rootroot00000000000000django-contact-form-2.0.1/tests/templates/django_contact_form/000077500000000000000000000000001424532576100245175ustar00rootroot00000000000000django-contact-form-2.0.1/tests/templates/django_contact_form/contact_form.html000066400000000000000000000000131424532576100300550ustar00rootroot00000000000000{{ body }} django-contact-form-2.0.1/tests/templates/django_contact_form/contact_form.txt000066400000000000000000000000121424532576100277270ustar00rootroot00000000000000{{ body }}django-contact-form-2.0.1/tests/templates/django_contact_form/contact_form_sent.html000066400000000000000000000000251424532576100311110ustar00rootroot00000000000000

Message sent!

django-contact-form-2.0.1/tests/templates/django_contact_form/contact_form_subject.txt000066400000000000000000000000241424532576100314510ustar00rootroot00000000000000Contact form messagedjango-contact-form-2.0.1/tests/templates/django_contact_form/test_callable_template_name.html000066400000000000000000000000351424532576100330740ustar00rootroot00000000000000Callable template_name used. django-contact-form-2.0.1/tests/test_forms.py000066400000000000000000000141711424532576100212640ustar00rootroot00000000000000import os import unittest from unittest import mock from django.conf import settings from django.core import mail from django.test import RequestFactory, TestCase from django_contact_form.forms import AkismetContactForm, ContactForm class ContactFormTests(TestCase): """ Tests the base ContactForm. """ valid_data = {"name": "Test", "email": "test@example.com", "body": "Test message"} def request(self): return RequestFactory().request() def test_request_required(self): """ Can't instantiate without an HttpRequest. """ self.assertRaises(TypeError, ContactForm) def test_valid_data_required(self): """ Can't try to build the message dict unless data is valid. """ data = {"name": "Test", "body": "Test message"} form = ContactForm(request=self.request(), data=data) self.assertRaises(ValueError, form.get_message_dict) self.assertRaises(ValueError, form.get_message_context) def test_send(self): """ Valid form can and does in fact send email. """ form = ContactForm(request=self.request(), data=self.valid_data) self.assertTrue(form.is_valid()) form.save() self.assertEqual(1, len(mail.outbox)) message = mail.outbox[0] self.assertTrue(self.valid_data["body"] in message.body) self.assertEqual(settings.DEFAULT_FROM_EMAIL, message.from_email) self.assertEqual(form.recipient_list, message.recipients()) def test_no_sites(self): """ Sites integration works with or without installed contrib.sites. """ with self.modify_settings(INSTALLED_APPS={"remove": ["django.contrib.sites"]}): form = ContactForm(request=self.request(), data=self.valid_data) self.assertTrue(form.is_valid()) form.save() self.assertEqual(1, len(mail.outbox)) def test_recipient_list(self): """ Passing recipient_list when instantiating ContactForm properly overrides the list of recipients. """ recipient_list = ["recipient_list@example.com"] form = ContactForm( request=self.request(), data=self.valid_data, recipient_list=recipient_list ) self.assertTrue(form.is_valid()) form.save() self.assertEqual(1, len(mail.outbox)) message = mail.outbox[0] self.assertEqual(recipient_list, message.recipients()) def test_callable_template_name(self): """ When a template_name() method is defined, it is used and preferred over a 'template_name' attribute. """ class CallableTemplateName(ContactForm): def template_name(self): return "django_contact_form/test_callable_template_name.html" form = CallableTemplateName(request=self.request(), data=self.valid_data) self.assertTrue(form.is_valid()) form.save() self.assertEqual(1, len(mail.outbox)) message = mail.outbox[0] self.assertTrue("Callable template_name used." in message.body) def test_callable_message_parts(self): """ Message parts implemented as methods are called and preferred over attributes. """ overridden_data = { "from_email": "override@example.com", "message": "Overridden message.", "recipient_list": ["override_recpt@example.com"], "subject": "Overridden subject", } class CallableMessageParts(ContactForm): def from_email(self): return overridden_data["from_email"] def message(self): return overridden_data["message"] def recipient_list(self): return overridden_data["recipient_list"] def subject(self): return overridden_data["subject"] form = CallableMessageParts(request=self.request(), data=self.valid_data) self.assertTrue(form.is_valid()) self.assertEqual(overridden_data, form.get_message_dict()) @unittest.skipUnless( getattr(settings, "AKISMET_API_KEY", os.getenv("PYTHON_AKISMET_API_KEY")) is not None, "AkismetContactForm requires Akismet configuration", ) class AkismetContactFormTests(TestCase): """ Tests the Akismet contact form. """ def request(self): return RequestFactory().request() def test_akismet_form_spam(self): """ The Akismet contact form correctly rejects spam. """ data = { "name": "viagra-test-123", "email": "email@example.com", "body": "This is spam.", } with mock.patch("akismet.Akismet", autospec=True) as akismet_mock: instance = akismet_mock.return_value instance.verify_key.return_value = True instance.comment_check.return_value = True form = AkismetContactForm(request=self.request(), data=data) self.assertFalse(form.is_valid()) self.assertTrue(str(form.SPAM_MESSAGE) in form.errors["body"]) def test_akismet_form_ham(self): """ The Akismet contact form correctly accepts non-spam. """ data = {"name": "Test", "email": "email@example.com", "body": "Test message."} with mock.patch("akismet.Akismet", autospec=True) as akismet_mock: instance = akismet_mock.return_value instance.verify_key.return_value = True instance.comment_check.return_value = False form = AkismetContactForm(request=self.request(), data=data) self.assertTrue(form.is_valid()) def test_akismet_form_no_body(self): """ The Akismet contact form correctly skips validation when no email body is provided. """ data = {"name": "Test", "email": "email@example.com"} with mock.patch("akismet.Akismet", autospec=True) as akismet_mock: form = AkismetContactForm(request=self.request(), data=data) self.assertFalse(form.is_valid()) akismet_mock.assert_not_called() self.assertFalse(form.is_valid()) django-contact-form-2.0.1/tests/test_urls.py000066400000000000000000000014671424532576100211270ustar00rootroot00000000000000""" URLConf for testing django-contact-form. """ from django.urls import path from django.views.generic import TemplateView from django_contact_form.forms import AkismetContactForm from django_contact_form.views import ContactFormView urlpatterns = [ path("", ContactFormView.as_view(), name="django_contact_form"), path( "sent/", TemplateView.as_view( template_name="django_contact_form/contact_form_sent.html" ), name="django_contact_form_sent", ), path( "test_recipient_list/", ContactFormView.as_view(recipient_list=["recipient_list@example.com"]), name="test_recipient_list", ), path( "test_akismet_form/", ContactFormView.as_view(form_class=AkismetContactForm), name="test_akismet_form", ), ] django-contact-form-2.0.1/tests/test_views.py000066400000000000000000000104441424532576100212720ustar00rootroot00000000000000import os import unittest from unittest import mock from django.conf import settings from django.core import mail from django.test import RequestFactory, TestCase from django.test.utils import override_settings from django.urls import reverse from django_contact_form.forms import ContactForm @override_settings(ROOT_URLCONF="tests.test_urls") class ContactFormViewTests(TestCase): def test_get(self): """ HTTP GET on the form view just shows the form. """ contact_url = reverse("django_contact_form") response = self.client.get(contact_url) self.assertEqual(200, response.status_code) self.assertTemplateUsed(response, "django_contact_form/contact_form.html") def test_send(self): """ Valid data through the view results in a successful send. """ contact_url = reverse("django_contact_form") data = {"name": "Test", "email": "test@example.com", "body": "Test message"} response = self.client.post(contact_url, data=data) self.assertRedirects(response, reverse("django_contact_form_sent")) self.assertEqual(1, len(mail.outbox)) message = mail.outbox[0] self.assertTrue(data["body"] in message.body) self.assertEqual(settings.DEFAULT_FROM_EMAIL, message.from_email) form = ContactForm(request=RequestFactory().request) self.assertEqual(form.recipient_list, message.recipients()) def test_invalid(self): """ Invalid data doesn't work. """ contact_url = reverse("django_contact_form") data = {"name": "Test", "body": "Test message"} response = self.client.post(contact_url, data=data) self.assertEqual(200, response.status_code) self.assertFormError(response, "form", "email", "This field is required.") self.assertEqual(0, len(mail.outbox)) def test_recipient_list(self): """ Passing recipient_list when instantiating ContactFormView properly overrides the list of recipients. """ contact_url = reverse("test_recipient_list") data = {"name": "Test", "email": "test@example.com", "body": "Test message"} response = self.client.post(contact_url, data=data) self.assertRedirects(response, reverse("django_contact_form_sent")) self.assertEqual(1, len(mail.outbox)) message = mail.outbox[0] self.assertEqual(["recipient_list@example.com"], message.recipients()) @unittest.skipUnless( getattr(settings, "AKISMET_API_KEY", os.getenv("PYTHON_AKISMET_API_KEY")) is not None, "AkismetContactForm requires Akismet configuration", ) @override_settings(ROOT_URLCONF="django_contact_form.akismet_urls") class AkismetContactFormViewTests(TestCase): """ Tests the views with the Akismet contact form. """ def test_akismet_view_spam(self): """ The Akismet contact form errors on spam. """ contact_url = reverse("django_contact_form") data = { "name": "viagra-test-123", "email": "email@example.com", "body": "This is spam.", } with mock.patch("akismet.Akismet", autospec=True) as akismet_mock: instance = akismet_mock.return_value instance.verify_key.return_value = True instance.comment_check.return_value = True response = self.client.post(contact_url, data=data) self.assertEqual(200, response.status_code) self.assertFalse(response.context["form"].is_valid()) self.assertTrue(response.context["form"].has_error("body")) def test_akismet_view_ham(self): contact_url = reverse("django_contact_form") data = {"name": "Test", "email": "email@example.com", "body": "Test message."} with mock.patch("akismet.Akismet", autospec=True) as akismet_mock: instance = akismet_mock.return_value instance.verify_key.return_value = True instance.comment_check.return_value = False response = self.client.post(contact_url, data=data) self.assertRedirects(response, reverse("django_contact_form_sent")) self.assertEqual(1, len(mail.outbox)) message = mail.outbox[0] self.assertEqual(["noreply@example.com"], message.recipients()) django-contact-form-2.0.1/tox.ini000066400000000000000000000150551424532576100167000ustar00rootroot00000000000000# This is a configuration file for running tests, linters and other # code-quality checks, using Tox (https://tox.readthedocs.io/), which # allows configuring and automatically running many different test # environments and checks, each in a separate Python virtual # environment (and each potentially using a different version of # Python). # # Using this file requires having tox installed -- "pip install tox" # or refer to Tox's own documentation -- and a functioning # installation of at least one targeted Python version. Running "tox" # with no command-line arguments will attempt to run all environments # against all targeted Python versions, and will fail if any Python # versions are missing. To select only certain test environments to # run, use the "-e" command-line flag and pass either a single # environment name, or a comma-separated list of environment names. To # see all available environments with their descriptions, run: # "tox -v 1 --listenvs" # Base configuration: list of environments and Python versions. ################################################################################ # Environment matrix. [tox] envlist = py37-django32 py{38,39,310}-django{32,40} black check-description check-manifest docs flake8 isort spelling # Configuration for running on GitHub Actions via tox-gh-actions. [gh-actions] python = 3.6: py36 3.7: py37 3.8: py38 3.9: py39 3.10: py310, black, check-description, check-manifest, docs, flake8, isort, spelling # The base test environment -- runs the unit test suite with coverage. ################################################################################ [testenv] description = Run tests with coverage report. allowlist_externals = find rm # Python silences deprecation warnings by default, but we want to see # them during test runs. setenv = PYTHONWARNINGS=once::DeprecationWarning # Ensure each virtualenv always has latest pip, so output doesn't get # cluttered with messages about needing to upgrade it. Note that tox's # 'download=true' option doesn't quite do the same thing: it ensures # pip/setuptools/wheel get upgraded at virtualenv creation time, but # will not upgrade them when reusing an already-created virtualenv. commands_pre = {envpython} -m pip install --upgrade pip # Many test runs will leave behind some type of artifact -- Python # bytecode, packaging-related files, coverage data -- which should be # removed before the next run in order to ensure a clean starting # point. The commands below run after the main test commands of each # virtualenv, and perform this cleanup. commands_post = find {toxinidir}/tests -type f -name "*.pyc" -delete find {toxinidir}/tests -type d -name "__pycache__" -delete find {toxinidir}/src -type f -name "*.pyc" -delete find {toxinidir}/src -type d -name "__pycache__" -delete find {toxinidir}/src -type f -path "*.egg-info*" -delete find {toxinidir}/src -type d -path "*.egg-info" -delete rm -f {toxinidir}/.coverage commands = coverage run --source django_contact_form runtests.py coverage report -m deps = akismet coverage django32: Django>=3.2,<4.0 django40: Django>=4.0,<4.1 passenv = PYTHON_AKISMET_API_KEY PYTHON_AKISMET_BLOG_URL # Documentation checks. ################################################################################ # Runs an HTML build of the documentation, and fails if there's an # error in building it. [testenv:docs] description = Check that the documentation can build. basepython = python3.10 changedir = {toxinidir}/docs commands = sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html deps = sphinx sphinx_rtd_theme # Runs a spelling checker over the documentation, and if misspelled # words are found, fails the build and outputs a list of # them. Requires the 'enchant' C library preinstalled on the target # system. [testenv:spelling] description = Spell-check documentation. basepython = python3.10 changedir = {toxinidir}/docs # This is the only env where we silence deprecation warnings, because # we'd already catch any from our actual codebase elsewhere and in # this env we are asking Sphinx to promote warnings to errors in order # to fail the build on anything caught by the spelling checker. setenv = PYTHONWARNINGS=ignore::DeprecationWarning commands = sphinx-build -W -b spelling -d {envtmpdir}/doctrees . {envtmpdir}/html deps = sphinx sphinx_rtd_theme pyenchant sphinxcontrib-spelling # Linters. ################################################################################ # Runs the Black code formatter over the entire code base, and fails # if Black thinks any files need to be reformatted. [testenv:black] description = Check code formatting using Black. basepython = python3.10 changedir = {toxinidir} deps = black commands = black --line-length 88 --check --diff {toxinidir}/src/django_contact_form {toxinidir}/tests {toxinidir}/docs {toxinidir} # Runs the flake8 linter over the entire code base, and fails if # flake8 finds any problems. [testenv:flake8] description = Lint code with flake8. basepython = python3.10 changedir = {toxinidir} deps = flake8 commands = flake8 {toxinidir}/src/django_contact_form {toxinidir}/tests # Runs the isort import linter over the entire code base, and fails if # any problems are found. [testenv:isort] description = Lint imports with isort. basepython = python3.10 changedir = {toxinidir} deps = isort commands = isort --check-only --diff {toxinidir}/src/django_contact_form {toxinidir}/tests # Packaging checks. ################################################################################ # Builds the package and runs 'twine check' to ensure it will render # correctly when uploaded to the Python Package Index, or fail if not. [testenv:check-description] description = Check that the package description will render on the Python Package Index. basepython = python3.10 changedir = {toxinidir} skip_install = true deps = twine # In this environment we always want latest wheel in addition to # latest pip. commands_pre = {envpython} -m pip install --upgrade pip setuptools wheel commands = {envpython} -m pip wheel -w {envtmpdir}/build --no-deps . twine check {envtmpdir}/build/* # Runs check-manifest, a tool that builds the package and compares the # files in the package to the files under version control, and fails # if any version-controlled files do not end up in the package. [testenv:check-manifest] description = Check that the set of packaged files matches the set of version-controlled files. basepython = python3.10 changedir = {toxinidir} skip_install = true deps = check-manifest commands = check-manifest --verbose