diff --git a/.gitignore b/.gitignore index 25a740f..0374977 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ *.pyc *.egg-info dist/ +docs/_build/ TODO.txt local.py diff --git a/README.rst b/README.rst index ea2bdbe..2f259df 100644 --- a/README.rst +++ b/README.rst @@ -1,327 +1,121 @@ -Djrill, for Mandrill -==================== +.. This README is reused in multiple places: + * Github: project page, exactly as it appears here + * Docs: shared-intro section gets included in docs/index.rst + quickstart section gets included in docs/quickstart.rst + * PyPI: project page (via setup.py long_description), + with several edits to freeze it to the specific PyPI release + (see long_description_from_readme in setup.py) + You can use docutils 1.0 markup, but *not* any Sphinx additions. -.. image:: https://secure.travis-ci.org/brack3t/Djrill.png?branch=master - :target: https://travis-ci.org/brack3t/Djrill +.. These substitution definitions apply in the readme (github) only; + they're altered by setup.py for the long_description, + and defined differently for the docs includes -Djrill is an email backend for Django users who want to take advantage of the -Mandrill_ transactional email service from MailChimp_. +.. |release| replace:: (source) -In general, Djrill "just works" with Django's built-in `django.core.mail`_ -package. You can also take advantage of Mandrill-specific features like tags, -metadata, and tracking. +.. |version| replace:: |release| -An optional Django admin interface is included. The admin interface allows you to: +.. |buildstatus| image:: https://secure.travis-ci.org/brack3t/Djrill.png?branch=master + :target: https://travis-ci.org/brack3t/Djrill -* Check the status of your Mandrill API connection. -* See stats on email senders, tags and urls. +.. default-role:: literal -Djrill is made available under the BSD license. -Installation +.. _shared-intro: + .. This shared-intro section is also included in docs/index.rst + +Djrill: Mandrill Transactional Email for Django +=============================================== + +Release |release| + +Djrill integrates the `Mandrill `_ transactional +email service into Django. + +In general, Djrill "just works" with Django's built-in `django.core.mail` +package. It includes: + +* Support for HTML, attachments, extra headers, and other features of + `Django's built-in email `_ +* Mandrill-specific extensions like tags, metadata, tracking, and MailChimp templates +* An optional Django admin interface + +Djrill is released under the BSD license. It is tested against Django 1.3, 1.4, and 1.5 +(including Python 3 support with Django 1.5). |buildstatus| + +.. END shared-intro + +Resources: + +* Full documentation: https://djrill.readthedocs.org/en/latest/ +* Package on PyPI: https://pypi.python.org/pypi/djrill +* Project on Github: https://github.com/brack3t/Djrill + + +Djrill 1-2-3 ------------ -Install from PyPI:: +.. _quickstart: + .. This quickstart section is also included in docs/quickstart.rst - pip install djrill +1. Install Djrill from PyPI: -The only dependency other than Django is the requests_ library from Kenneth -Reitz. (If you do not install Djrill using pip or setuptools, you will also -need to ``pip install requests``.) + .. code-block:: console + $ pip install djrill -Configuration -------------- -In ``settings.py``: +2. Edit your project's ``settings.py``: -1. Add ``djrill`` to your ``INSTALLED_APPS``: + .. code-block:: python -.. code:: python + INSTALLED_APPS = ( + ... + "djrill" + ) - INSTALLED_APPS = ( - ... - "djrill" - ) + MANDRILL_API_KEY = "" + EMAIL_BACKEND = "djrill.mail.backends.djrill.DjrillBackend" -2. Add the following line, substituting your own ``MANDRILL_API_KEY``: -.. code:: python +3. Now the regular `Django email functions `_ + will send through Mandrill: - MANDRILL_API_KEY = "brack3t-is-awesome" + .. code-block:: python -3. Override your existing email backend with the following line: + from django.core.mail import send_mail -.. code:: python + send_mail("It works!", "This will get sent through Mandrill", + "Djrill Sender ", ["to@example.com"]) - EMAIL_BACKEND = "djrill.mail.backends.djrill.DjrillBackend" -4. (optional) If you want to be able to add senders through Django's admin or - view stats about your messages, do the following in your base ``urls.py``: + You could send an HTML message, complete with custom Mandrill tags and metadata: -.. code:: python + .. code-block:: python - ... - from django.contrib import admin + from django.core.mail import EmailMultiAlternatives - from djrill import DjrillAdminSite + msg = EmailMultiAlternatives( + subject="Djrill Message", + body="This is the text email body", + from_email="Djrill Sender ", + to=["Recipient One ", "another.person@example.com"], + headers={'Reply-To': "Service "} # optional extra headers + ) + msg.attach_alternative("

This is the HTML email body

", "text/html") - admin.site = DjrillAdminSite() - admin.autodiscover() - ... + # Optional Mandrill-specific extensions: + msg.tags = ["one tag", "two tag", "red tag", "blue tag"] + msg.metadata = {'user_id': "8675309"} - urlpatterns = patterns('', - ... - url(r'^admin/', include(admin.site.urls)), - ) + # Send it: + msg.send() -Usage ------ + (Be sure to use a ``from_email`` that's in one of your Mandrill approved sending + domains, or the message won't get sent.) -Since you are replacing the global ``EMAIL_BACKEND``, **all** emails are sent -through Mandrill's service. (To selectively use Mandrill for some messages, see -`Using Multiple Email Backends`_ below.) +.. END quickstart -In general, Djrill "just works" with Django's built-in `django.core.mail`_ -package, including ``send_mail``, ``send_mass_mail``, ``EmailMessage`` and -``EmailMultiAlternatives``. - -You can also take advantage of Mandrill-specific features like tags, metadata, -and tracking by creating a Django EmailMessage_ (or for HTML, -EmailMultiAlternatives_) object and setting Mandrill-specific -properties on it before calling its ``send`` method. (See -`Mandrill Message Options`_ below.) - -Example, sending HTML email with Mandrill tags and metadata: - -.. code:: python - - from django.core.mail import EmailMultiAlternatives - - msg = EmailMultiAlternatives( - subject="Djrill Message", - body="This is the text version of your email", - from_email="Djrill Sender ", - to=["Djrill Receiver ", "another.person@example.com"], - headers={'Reply-To': "Service "} # optional extra headers - ) - msg.attach_alternative("

This is the HTML version of your email

", "text/html") - - # Optional Mandrill-specific extensions (see full list below): - msg.tags = ["one tag", "two tag", "red tag", "blue tag"] - msg.metadata = {'user_id': "8675309"} - - # Send it: - msg.send() - -If the email tries to use features that aren't supported by Mandrill, the send -call will raise a ``djrill.NotSupportedByMandrillError`` exception (a subclass -of ValueError). - -If the Mandrill API fails or returns an error response, the send call will -raise a ``djrill.MandrillAPIError`` exception (a subclass of -requests.HTTPError). - - -Django EmailMessage Support -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Djrill supports most of the functionality of Django's `EmailMessage`_ and -`EmailMultiAlternatives`_ classes. Some notes and limitations: - -* **Display Names:** All email addresses (from, to, cc) can be simple - ("email@example.com") or can include a display name - ("Real Name "). -* **From Address:** The ``from_email`` must be in one of the approved sending - domains in your Mandrill account. -* **CC Recipients:** Djrill treats all "cc" recipients as if they were - additional "to" addresses. (Mandrill does not distinguish "cc" from "to".) - Note that you will also need to set ``preserve_recipients`` True if you want - each recipient to see the other recipients listed in the email headers. -* **BCC Recipients:** Mandrill does not permit more than one "bcc" address. - Djrill raises ``djrill.NotSupportedByMandrillError`` if you attempt to send a - message with multiple bcc's. (Mandrill's bcc option seems intended primarily - for logging. To send a single message to multiple recipients without exposing - their email addresses to each other, simply include them all in the "to" list - and leave ``preserve_recipients`` set to False.) -* **Attachments:** Djrill includes a message's attachments. Also, if an image - attachment has a Content-ID header, Djrill will tell Mandrill to treat that - as an embedded image rather than an ordinary attachment. (For an example, - see ``test_embedded_images`` in tests/test_mandrill_send.py.) -* **Headers:** Djrill accepts additional headers, but only ``Reply-To`` and - ``X-*`` (since that is all that Mandrill accepts). Any other extra headers - will raise ``djrill.NotSupportedByMandrillError`` when you attempt to send the - message. -* **Alternative Parts:** Djrill requires that if you ``attach_alternative`` to a - message, there must be only one alternative part, and it must be text/html. - Otherwise, Djrill will raise ``djrill.NotSupportedByMandrillError`` when you - attempt to send the message. (Mandrill doesn't support sending multiple html - alternative parts, or any non-html alternatives.) - -Mandrill Message Options -~~~~~~~~~~~~~~~~~~~~~~~~ - -Many of the options from the Mandrill `messages/send API`_ ``message`` -struct can be set directly on an ``EmailMessage`` (or subclass) object: - -* ``track_opens`` - Boolean -* ``track_clicks`` - Boolean (If you want to track clicks in HTML only, not - plaintext mail, you must *not* set this property, and instead just set the - default in your Mandrill account sending options.) -* ``auto_text`` - Boolean -* ``url_strip_qs`` - Boolean -* ``preserve_recipients`` - Boolean -* ``global_merge_vars`` - a dict -- e.g., - ``{ 'company': "ACME", 'offer': "10% off" }`` -* ``recipient_merge_vars`` - a dict whose keys are the recipient email addresses - and whose values are dicts of merge vars for each recipient -- e.g., - ``{ 'wiley@example.com': { 'offer': "15% off anvils" } }`` -* ``tags`` - a list of strings -* ``google_analytics_domains`` - a list of string domain names -* ``google_analytics_campaign`` - a string or list of strings -* ``metadata`` - a dict -* ``recipient_metadata`` - a dict whose keys are the recipient email addresses, - and whose values are dicts of metadata for each recipient (similar to - ``recipient_merge_vars``) - -These Mandrill-specific properties work with *any* ``EmailMessage``-derived -object, so you can use them with many other apps that add Django mail -functionality (such as Django template-based messages). - -If you have any questions about the python syntax for any of these properties, -see ``DjrillMandrillFeatureTests`` in tests/test_mandrill_send.py for examples. - -Mandrill Templates -~~~~~~~~~~~~~~~~~~ - -To use a Mandrill (MailChimp) template, set a ``template_name`` and (optionally) -``template_content`` on your ``EmailMessage`` object: - -.. code:: python - - msg = EmailMessage(subject="Shipped!", from_email="store@example.com", - to=["customer@example.com", "accounting@example.com"]) - msg.template_name = "SHIPPING_NOTICE" # A Mandrill template name - msg.template_content = { # Content blocks to fill in - 'TRACKING_BLOCK': "track it" } - msg.global_merge_vars = { # Merge tags in your template - 'ORDERNO': "12345", 'TRACKINGNO': "1Z987" } - msg.merge_vars = { # Per-recipient merge tags - 'accounting@example.com': { 'NAME': "Pat" }, - 'customer@example.com': { 'NAME': "Kim" } } - msg.send() - -If template_name is set, Djrill will use Mandrill's `messages/send-template API`_, -rather than messages/send. All of the other options listed above can be used. - -(This is for *MailChimp* templates stored in your Mandrill account. If you -want to use a *Django* template, you can use Django's render_to_string_ template -shortcut to build the body and html, and send using EmailMultiAlternatives as -in the earlier examples.) - -Using Multiple Email Backends -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -You can use Django mail's optional ``connection`` argument to send some mail -through Mandrill and others through a different system. This can be useful to -send customer emails with Mandrill, but admin emails directly through an SMTP -server. Example: - -.. code:: python - - from django.core.mail import send_mail, get_connection - - # send_mail connection defaults to the settings EMAIL_BACKEND, which - # we've set to DjrillBackend. This will be sent using Mandrill: - send_mail("Subject", "Body", "support@example.com", ["user@example.com"]) - - # Get a connection to an SMTP backend, and send using that instead: - smtp_backend = get_connection('django.core.mail.backends.smtp.EmailBackend') - send_mail("Subject", "Body", "admin@example.com", ["alert@example.com"], - connection=smtp_backend) - -You can supply a different connection to Django's `django.core.mail`_ -``send_mail`` and ``send_mass_mail`` helpers, and in the constructor for an -EmailMessage_ or EmailMultiAlternatives_. - - -Testing -------- - -Djrill is tested against Django 1.3 and 1.4 on Python 2.6 and 2.7, and -Django 1.5RC on Python 2.7 and 3.2. -(It may also work with Django 1.2 and Python 2.5, if you use an older -version of requests compatible with that code.) - -.. image:: https://secure.travis-ci.org/brack3t/Djrill.png?branch=master - :target: https://travis-ci.org/brack3t/Djrill - -The included tests verify that Djrill constructs the expected Mandrill API -calls, without actually calling Mandrill or sending any email. So the tests -don't require a Mandrill API key, but they *do* require mock_ -(``pip install mock``). To run the tests, either:: - - python -Wall setup.py test - -or:: - - python -Wall runtests.py - - -Contributing ------------- - -Djrill is maintained by its users -- it's not managed by the folks at MailChimp. -Pull requests are always welcome to improve support for Mandrill and Django -features. - -Please include test cases with pull requests. (And by submitting a pull request, -you're agreeing to release your changes under under the same BSD license as the -rest of this project.) - - -Release Notes -------------- - -Version 0.3.0: - -* Attachments are now supported -* Mandrill templates are now supported -* A bcc address is now passed to Mandrill as bcc, rather than being lumped in - with the "to" recipients. Multiple bcc recipients will now raise an exception, - as Mandrill only allows one. -* Python 3 support (with Django 1.5) -* Exceptions should be more useful: ``djrill.NotSupportedByMandrillError`` - replaces generic ValueError; ``djrill.MandrillAPIError`` replaces - DjrillBackendHTTPError, and is now derived from requests.HTTPError. (New - exceptions are backwards compatible with old ones for existing code.) - -Version 0.2.0: - -* ``MANDRILL_API_URL`` is no longer required in settings.py -* Earlier versions of Djrill required use of a ``DjrillMessage`` class to - specify Mandrill-specific options. This is no longer needed -- Mandrill - options can now be set directly on a Django EmailMessage_ object or any - subclass. (Existing code can continue to use ``DjrillMessage``.) - - -Thanks ------- - -Thanks to the MailChimp team for asking us to build this nifty little app. Also thanks to James Socol on Github for his -django-adminplus_ library that got us off on the right foot for the custom admin views. Oh, and, of course, Kenneth Reitz for -the awesome ``requests`` library. - - -.. _Mandrill: http://mandrill.com -.. _MailChimp: http://mailchimp.com -.. _requests: http://docs.python-requests.org -.. _django-adminplus: https://github.com/jsocol/django-adminplus -.. _mock: http://www.voidspace.org.uk/python/mock/index.html -.. _django.core.mail: https://docs.djangoproject.com/en/dev/topics/email/ -.. _EmailMessage: https://docs.djangoproject.com/en/dev/topics/email/#django.core.mail.EmailMessage -.. _EmailMultiAlternatives: https://docs.djangoproject.com/en/dev/topics/email/#sending-alternative-content-types -.. _render_to_string: https://docs.djangoproject.com/en/dev/ref/templates/api/#the-render-to-string-shortcut -.. _messages/send API: https://mandrillapp.com/api/docs/messages.html#method=send -.. _messages/send-template API: https://mandrillapp.com/api/docs/messages.html#method=send-template +See the `full documentation `_ +for more features and options. diff --git a/djrill/__init__.py b/djrill/__init__.py index 565b557..b8acf10 100644 --- a/djrill/__init__.py +++ b/djrill/__init__.py @@ -4,8 +4,7 @@ from django.utils.text import capfirst from djrill.exceptions import MandrillAPIError, NotSupportedByMandrillError -VERSION = (0, 3, 1) -__version__ = '.'.join([str(x) for x in VERSION]) +from ._version import * # This backend was developed against this API endpoint. # You can override in settings.py, if desired. diff --git a/djrill/_version.py b/djrill/_version.py new file mode 100644 index 0000000..a26bd62 --- /dev/null +++ b/djrill/_version.py @@ -0,0 +1,3 @@ +VERSION = (0, 3, 9) +__version__ = '.'.join([str(x) for x in VERSION]) +__minor_version__ = '.'.join([str(x) for x in VERSION[:2]]) # Sphinx's X.Y "version" diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..917a233 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# 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/Djrill.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Djrill.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/Djrill" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Djrill" + @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." diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..afdfb74 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,282 @@ +# -*- coding: utf-8 -*- +# +# Djrill documentation build configuration file, created by +# sphinx-quickstart on Sat Mar 2 13:07:34 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('..')) + +# define __version__ and __minor_version__ from ../djrill/_version.py, +# but without importing from djrill (which would make docs dependent on Django, etc.) +with open("../djrill/_version.py") as f: + code = compile(f.read(), "../djrill/_version.py", 'exec') + exec(code) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.intersphinx'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Djrill' +# noinspection PyShadowingBuiltins +copyright = u'2013, Djrill contributors (see AUTHORS.txt)' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = __minor_version__ +# The full version, including alpha/beta/rc tags. +release = __version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +default_role = "py:obj" + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Djrilldoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Djrill.tex', u'Djrill Documentation', + u'Djrill contributors (see AUTHORS.txt)', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'djrill', u'Djrill Documentation', + [u'Djrill contributors (see AUTHORS.txt)'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Djrill', u'Djrill Documentation', + u'Djrill contributors (see AUTHORS.txt)', 'Djrill', 'Mandrill integration for Django.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# -- Options for Intersphinx ------------------------------------------------ + +intersphinx_mapping = { + 'python': ('http://docs.python.org/2.7', None), + 'django': ('http://docs.djangoproject.com/en/dev/', 'http://docs.djangoproject.com/en/dev/_objects/'), + 'requests': ('http://docs.python-requests.org/en/latest/', None), +} + + +def setup(app): + # Django-specific roles, from https://github.com/django/django/blob/master/docs/_ext/djangodocs.py: + app.add_crossref_type( + directivename = "setting", + rolename = "setting", + indextemplate = "pair: %s; setting", + ) + app.add_crossref_type( + directivename = "templatetag", + rolename = "ttag", + indextemplate = "pair: %s; template tag" + ) + app.add_crossref_type( + directivename = "templatefilter", + rolename = "tfilter", + indextemplate = "pair: %s; template filter" + ) + app.add_crossref_type( + directivename = "fieldlookup", + rolename = "lookup", + indextemplate = "pair: %s; field lookup type", + ) diff --git a/docs/contributing.rst b/docs/contributing.rst new file mode 100644 index 0000000..218778e --- /dev/null +++ b/docs/contributing.rst @@ -0,0 +1,54 @@ +Contributing +============ + +Djrill is maintained by its users. Your contributions are encouraged! + +The `Djrill source code`_ is on github. See `AUTHORS.txt`_ for a list +of some of the people who have helped improve Djrill. + +.. _Djrill source code: https://github.com/brack3t/Djrill: +.. _AUTHORS.txt: https://github.com/brack3t/Djrill/blob/master/AUTHORS.txt + + +Bugs +---- + +You can report problems or request features in +`Djrill's github issue tracker `_. + + +Pull Requests +------------- + +Pull requests are always welcome to fix bugs and improve support for Mandrill and Django features. + +* Please include test cases. +* We try to follow the `Django coding style`_ (basically, PEP 8 with longer lines OK). +* By submitting a pull request, you're agreeing to release your changes under under + the same BSD license as the rest of this project. + +.. _Django coding style: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/ + + +Testing +------- + +Djrill is `tested on Travis `_ against: + +* Django 1.3 on Python 2.6 and 2.7 +* Django 1.4 on Python 2.6 and 2.7 +* Django 1.5 on Python 2.7 and 3.2 + +The included tests verify that Djrill constructs the expected Mandrill API +calls, without actually calling Mandrill or sending any email. So the tests +don't require a Mandrill API key, but they *do* require +`mock `_ (``pip install mock``). + +To run the tests, either:: + + python -Wall setup.py test + +or:: + + python -Wall runtests.py + diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 0000000..4bc6876 --- /dev/null +++ b/docs/history.rst @@ -0,0 +1,25 @@ +Release Notes +============= + +Version 0.3.0: + +* :ref:`Attachments ` are now supported +* :ref:`Mandrill templates ` are now supported +* A bcc address is now passed to Mandrill as bcc, rather than being lumped in + with the "to" recipients. Multiple bcc recipients will now raise an exception, + as Mandrill only allows one. +* Python 3 support (with Django 1.5) +* Exceptions should be more useful: + :exc:`djrill.NotSupportedByMandrillError` replaces generic ValueError; + :exc:`djrill.MandrillAPIError` replaces DjrillBackendHTTPError, and is now + derived from requests.HTTPError. + (New exceptions are backwards compatible with old ones for existing code.) + +Version 0.2.0: + +* ``MANDRILL_API_URL`` is no longer required in settings.py +* Earlier versions of Djrill required use of a ``DjrillMessage`` class to + specify Mandrill-specific options. This is no longer needed -- Mandrill + options can now be set directly on a Django ``EmailMessage`` object or any + subclass. (Existing code can continue to use ``DjrillMessage``.) + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..d10c888 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,45 @@ +.. Djrill documentation master file, created by + sphinx-quickstart on Sat Mar 2 13:07:34 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +.. Incorporate the shared-intro section from the root README: + +.. include:: ../README.rst + :start-after: _shared-intro: + :end-before: END shared-intro + +.. Eliminate the README's Travis build status indicator. + (Is there some way we could link to Travis status for the + specific |VERSION| of the app being documented here???) + +.. |buildstatus| replace:: \ + + +.. _toc: + +Documentation +------------- + +.. toctree:: + :maxdepth: 2 + + quickstart + installation + usage/sending_mail + usage/templates + usage/multiple_backends + contributing + history + + +Thanks +------ + +Thanks to the MailChimp team for asking us to build this nifty little app, and to all of Djrill's +:doc:`contributors `. Also thanks to James Socol on Github for his django-adminplus_ +library that got us off on the right foot for the custom admin views. +Oh, and, of course, Kenneth Reitz for the awesome requests_ library. + +.. _requests: http://docs.python-requests.org +.. _django-adminplus: https://github.com/jsocol/django-adminplus diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..d9af9ac --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,61 @@ +Installation +============ + +It's easiest to install Djrill from `PyPI `_: + + .. code-block:: console + + $ pip install djrill + +If you decide to install Djrill some other way, you'll also need to install its +one dependency (other than Django, of course): the `requests `_ +library from Kenneth Reitz. + + +Configuration +------------- + +In your project's :file:`settings.py`: + +1. Add :mod:`djrill` to your :setting:`INSTALLED_APPS`:: + + INSTALLED_APPS = ( + ... + "djrill" + ) + +2. Add the following line, substituting your own :setting:`MANDRILL_API_KEY`:: + + MANDRILL_API_KEY = "brack3t-is-awesome" + +3. Override your existing :setting:`EMAIL_BACKEND` with the following line:: + + EMAIL_BACKEND = "djrill.mail.backends.djrill.DjrillBackend" + + +Admin (Optional) +---------------- + +Djrill includes an optional Django admin interface, which allows you to: + +* Check the status of your Mandrill API connection +* See stats on email senders, tags and urls + +If you want to enable the Djrill admin interface, edit your base :file:`urls.py`: + + .. code-block:: python + :emphasize-lines: 4,6 + + ... + from django.contrib import admin + + from djrill import DjrillAdminSite + + admin.site = DjrillAdminSite() + admin.autodiscover() + ... + + urlpatterns = patterns('', + ... + url(r'^admin/', include(admin.site.urls)), + ) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..4090742 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,190 @@ +@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\Djrill.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Djrill.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 diff --git a/docs/quickstart.rst b/docs/quickstart.rst new file mode 100644 index 0000000..06be696 --- /dev/null +++ b/docs/quickstart.rst @@ -0,0 +1,9 @@ +Djrill 1-2-3 +============ + +.. Quickstart is maintained in README.rst at the source root. + (Docs can include from the readme; the readme can't include anything.) + +.. include:: ../README.rst + :start-after: _quickstart: + :end-before: END quickstart diff --git a/docs/usage/multiple_backends.rst b/docs/usage/multiple_backends.rst new file mode 100644 index 0000000..025d950 --- /dev/null +++ b/docs/usage/multiple_backends.rst @@ -0,0 +1,32 @@ +.. _multiple-backends: + +Mixing Email Backends +===================== + +Since you are replacing Django's global :setting:`EMAIL_BACKEND`, by default +Djrill will handle all outgoing mail, sending everything through Mandrill. + +You can use Django mail's optional :func:`connection ` +argument to send some mail through Mandrill and others through a different system. + +This could be useful, for example, to deliver customer emails with Mandrill, +but send admin emails directly through an SMTP server: + +.. code-block:: python + :emphasize-lines: 8,10 + + from django.core.mail import send_mail, get_connection + + # send_mail connection defaults to the settings EMAIL_BACKEND, which + # we've set to DjrillBackend. This will be sent using Mandrill: + send_mail("Thanks", "We sent your order", "sales@example.com", ["customer@example.com"]) + + # Get a connection to an SMTP backend, and send using that instead: + smtp_backend = get_connection('django.core.mail.backends.smtp.EmailBackend') + send_mail("Uh-Oh", "Need your attention", "admin@example.com", ["alert@example.com"], + connection=smtp_backend) + +You can supply a different connection to Django's +:func:`~django.core.mail.send_mail` and :func:`~django.core.mail.send_mass_mail` helpers, +and in the constructor for an +:class:`~django.core.mail.EmailMessage` or :class:`~django.core.mail.EmailMultiAlternatives`. diff --git a/docs/usage/sending_mail.rst b/docs/usage/sending_mail.rst new file mode 100644 index 0000000..b8e5bca --- /dev/null +++ b/docs/usage/sending_mail.rst @@ -0,0 +1,225 @@ +Sending Mail +============ + +Djrill handles **all** outgoing email sent through Django's standard +:mod:`django.core.mail` package, including :func:`~django.core.mail.send_mail`, +:func:`~django.core.mail.send_mass_mail`, the :class:`~django.core.mail.EmailMessage` class, +and even :func:`~django.core.mail.mail_admins`. + +If you'd like to selectively send only some messages through Mandrill, +there is a way to :ref:`use multiple email backends `. + + +.. _django-send-support: + +Django Email Support +-------------------- + +Djrill supports most of the functionality of Django's :class:`~django.core.mail.EmailMessage` +and :class:`~django.core.mail.EmailMultiAlternatives` classes. + +Some notes and limitations: + +**Display Names** + All email addresses (from, to, cc) can be simple + ("email\@example.com") or can include a display name + ("Real Name "). + +**From Address** + The ``from_email`` must be in one of the approved sending + domains in your Mandrill account, or Mandrill will refuse to send the message. + +**CC Recipients** + Djrill treats all "cc" recipients as if they were + additional "to" addresses. (Mandrill does not distinguish "cc" from "to".) + + .. note:: + + By default, Mandrill hides all recipients from each other. If you want the + headers to list everyone who was sent the message, you'll also need to set the + Mandrill option :attr:`preserve_recipients` to `!True` + +**BCC Recipients** + Mandrill does not permit more than one "bcc" address. + Djrill raises :exc:`~djrill.NotSupportedByMandrillError` if you attempt to send a + message with multiple bcc's. + + (Mandrill's bcc option seems intended primarily + for logging. To send a single message to multiple recipients without exposing + their email addresses to each other, simply include them all in the "to" list + and leave Mandrill's :attr:`preserve_recipients` set to `!False`.) + + .. versionadded:: 0.3 + Previously "bcc" was treated as "cc" + + +.. _sending-html: + +**HTML/Alternative Parts** + To include an HTML version of a message, use + :meth:`~django.core.mail.EmailMultiAlternatives.attach_alternative`: + + .. code-block:: python + + from django.core.mail import EmailMultiAlternatives + + msg = EmailMultiAlternatives("Subject", "text body", + "from@example.com", ["to@example.com"]) + msg.attach_alternative("html body", "text/html") + + Djrill allows a maximum of one + :meth:`~django.core.mail.EmailMultiAlternatives.attach_alternative` + on a message, and it must be ``mimetype="text/html"``. + Otherwise, Djrill will raise :exc:`~djrill.NotSupportedByMandrillError` when you + attempt to send the message. (Mandrill doesn't support sending multiple html + alternative parts, or any non-html alternatives.) + + +.. _sending-attachments: + +**Attachments** + Djrill will send a message's attachments. (Note that Mandrill may impose limits + on size and type of attachments.) + + Also, if an image attachment has a Content-ID header, Djrill will tell Mandrill + to treat that as an embedded image rather than an ordinary attachment. + (For an example, see :meth:`~DjrillBackendTests.test_embedded_images` + in :file:`tests/test_mandrill_send.py`.) + + .. versionadded:: 0.3 + Attachments + + .. versionchanged:: 0.4 + Special handling for embedded images + +**Headers** + Djrill accepts additional headers, but only ``Reply-To`` and + ``X-*`` (since that is all that Mandrill accepts). Any other extra headers + will raise :exc:`~djrill.NotSupportedByMandrillError` when you attempt to send the + message. + + +.. _mandrill-send-support: + +Mandrill-Specific Options +------------------------- + +Most of the options from the Mandrill +`messages/send API `_ +`message` struct can be set directly on an :class:`~django.core.mail.EmailMessage` +(or subclass) object: + +.. attribute:: track_opens + + ``Boolean``: whether Mandrill should enable open-tracking for this message. + Default from your Mandrill account settings. :: + + message.track_opens = True + +.. attribute:: track_clicks + + ``Boolean``: whether Mandrill should enable click-tracking for this message. + Default from your Mandrill account settings. + + .. note:: + + Mandrill has an option to track clicks in HTML email but not plaintext, but + it's *only* available in your Mandrill account settings. If you want to use that + option, set it at Mandrill, and *don't* set the ``track_clicks`` attribute here. + +.. attribute:: auto_text + + ``Boolean``: whether Mandrill should automatically generate a text body from the HTML. + Default from your Mandrill account settings. + +.. attribute:: url_strip_qs + + ``Boolean``: whether Mandrill should ignore any query parameters when aggregating + URL tracking data. Default from your Mandrill account settings. + +.. attribute:: preserve_recipients + + ``Boolean``: whether Mandrill should include all recipients in the "to" message header. + Default from your Mandrill account settings. + +.. attribute:: global_merge_vars + + ``dict``: merge variables to use for all recipients (most useful with :ref:`mandrill-templates`). :: + + message.global_merge_vars = {'company': "ACME", 'offer': "10% off"} + +.. attribute:: recipient_merge_vars + + ``dict``: per-recipient merge variables (most useful with :ref:`mandrill-templates`). The keys + in the dict are the recipient email addresses, and the values are dicts of merge vars for + each recipient:: + + message.recipient_merge_vars = { + 'wiley@example.com': {'offer': "15% off anvils"}, + 'rr@example.com': {'offer': "instant tunnel paint"} + } + +.. attribute:: tags + + ``list`` of ``str``: tags to apply to the message, for filtering reports in the Mandrill + dashboard. (Note that Mandrill prohibits tags longer than 50 characters or starting with + underscores.) :: + + message.tags = ["Order Confirmation", "Test Variant A"] + +.. attribute:: google_analytics_domains + + ``list`` of ``str``: domain names for links where Mandrill should add Google Analytics + tracking parameters. :: + + message.google_analytics_domains = ["example.com"] + +.. attribute:: google_analytics_campaign + + ``str`` or ``list`` of ``str``: the utm_campaign tracking parameter to attach to links + when adding Google Analytics tracking. (Mandrill defaults to the message's from_email as + the campaign name.) + +.. attribute:: metadata + + ``dict``: metadata values Mandrill should store with the message for later search and + retrieval. :: + + message.metadata = {'customer': customer.id, 'order': order.reference_number} + +.. attribute:: recipient_metadata + + ``dict``: per-recipient metadata values. Keys are the recipient email addresses, + and values are dicts of metadata for each recipient (similar to + :attr:`recipient_merge_vars`) + + +These Mandrill-specific properties work with *any* +:class:`~django.core.mail.EmailMessage`-derived object, so you can use them with +many other apps that add Django mail functionality. + +If you have questions about the python syntax for any of these properties, +see :class:`DjrillMandrillFeatureTests` in :file:`tests/test_mandrill_send.py` for examples. + + +.. _djrill-exceptions: + +Exceptions +---------- + +.. versionadded:: 0.3 + Djrill-specific exceptions + +.. exception:: djrill.NotSupportedByMandrillError + + If the email tries to use features that aren't supported by Mandrill, the send + call will raise a :exc:`~!djrill.NotSupportedByMandrillError` exception (a subclass + of :exc:`ValueError`). + + +.. exception:: djrill.MandrillAPIError + + If the Mandrill API fails or returns an error response, the send call will + raise a :exc:`~!djrill.MandrillAPIError` exception (a subclass of :exc:`requests.HTTPError`). + The exception's :attr:`status_code` and :attr:`response` attributes may + help explain what went wrong. diff --git a/docs/usage/templates.rst b/docs/usage/templates.rst new file mode 100644 index 0000000..368f0e8 --- /dev/null +++ b/docs/usage/templates.rst @@ -0,0 +1,70 @@ +Sending Template Mail +===================== + +.. _mandrill-templates: + +Mandrill Templates +------------------ + +.. versionadded:: 0.3 + Mandrill template support + +To use a *Mandrill* (MailChimp) template stored in your Mandrill account, +set a :attr:`template_name` and (optionally) :attr:`template_content` +on your :class:`~django.core.mail.EmailMessage` object:: + + from django.core.mail import EmailMessage + + msg = EmailMessage(subject="Shipped!", from_email="store@example.com", + to=["customer@example.com", "accounting@example.com"]) + msg.template_name = "SHIPPING_NOTICE" # A Mandrill template name + msg.template_content = { # Content blocks to fill in + 'TRACKING_BLOCK': "track it" + } + msg.global_merge_vars = { # Merge tags in your template + 'ORDERNO': "12345", 'TRACKINGNO': "1Z987" + } + msg.merge_vars = { # Per-recipient merge tags + 'accounting@example.com': {'NAME': "Pat"}, + 'customer@example.com': {'NAME': "Kim"} + } + msg.send() + +If :attr:`template_name` is set, Djrill will use Mandrill's +`messages/send-template API `_, +and will ignore any `body` text set on the `EmailMessage`. + +All of Djrill's other :ref:`Mandrill-specific options ` +can be used with templates. + + +.. _django-templates: + +Django Templates +---------------- + +To compose email using *Django* templates, you can use Django's +:func:`~django.template.loaders.django.template.loader.render_to_string` +template shortcut to build the body and html. + +Example that builds an email from the templates ``message_subject.txt``, +``message_body.txt`` and ``message_body.html``:: + + from django.core.mail import EmailMultiAlternatives + from django.template import Context + from django.template.loader import render_to_string + + template_data = { + 'ORDERNO': "12345", 'TRACKINGNO': "1Z987" + } + + plaintext_context = Context(autoescape=False) # HTML escaping not appropriate in plaintext + subject = render_to_string("message_subject.txt", template_data, plaintext_context) + text_body = render_to_string("message_body.txt", template_data, plaintext_context) + html_body = render_to_string("message_body.html", template_data) + + msg = EmailMessage(subject=subject, from_email="store@example.com", + to=["customer@example.com"], body=text_body) + msg.attach_alternative(html_body, "text/html") + msg.send() + diff --git a/setup.py b/setup.py index 34a0c0a..b29c789 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,31 @@ from setuptools import setup +import re -with open('LICENSE') as file: - license_text = file.read() -with open('README.rst') as file: - long_description = file.read() +# define __version__ and __minor_version__ from djrill/_version.py, +# but without importing from djrill (which would break setup) +with open("djrill/_version.py") as f: + code = compile(f.read(), "djrill/_version.py", 'exec') + exec(code) + + +def long_description_from_readme(rst): + # Patch up some rest substitution variables (references only - not definitions): + rst = re.sub(r'(?, Chris Jones ", author_email="kenneth@brack3t.com",