Update "Want to help?" form to support additional languages

fix bug 756093

- Move logic to email_contribute.py
- Refactored data structures to conform a bit more to DRY
- Remove newsletter options on non 'en' locales (fix bug 770606)
- Tests ! (fix bug 780911)
- Email backend in DEV environments writes to the console to avoid
unnecessary email sending
- PEP8 cleanups
- Fixes a dotlang test that depended on en-US
This commit is contained in:
Anthony Ricaud 2012-08-17 17:05:11 +02:00
Родитель 30eec568cb
Коммит d41980b08d
12 изменённых файлов: 385 добавлений и 215 удалений

Просмотреть файл

@ -0,0 +1,169 @@
from collections import namedtuple
from django.core.mail import EmailMessage
import basket
import jingo
from jinja2.exceptions import TemplateNotFound
from l10n_utils.dotlang import _lazy as _
fa = namedtuple('FunctionalArea', ['id', 'name', 'contacts'])
FUNCTIONAL_AREAS = (
fa('support',
_('Helping Users'),
['jay@jaygarcia.com', 'rardila@mozilla.com', 'madasan@gmail.com'],
),
fa('qa',
_('Testing and QA'),
['qa-contribute@mozilla.org'],
),
fa('coding',
_('Coding'),
['josh@joshmatthews.net'],
),
fa('marketing',
_('Marketing'),
['cnovak@mozilla.com'],
),
fa('localization',
_('Localization and Translation'),
['rardila@mozilla.com', 'jbeatty@mozilla.com', 'arky@mozilla.com'],
),
fa('webdev',
_('Web Development'),
['luke.crouch@gmail.com'],
),
fa('addons',
_('Add-ons'),
['atsay@mozilla.com'],
),
fa('design',
_('Visual Design'),
['mnovak@mozilla.com'],
),
fa('documentation',
_('Developer Documentation'),
['jswisher@mozilla.com'],
),
fa('accessibility',
_('Accessibility'),
['jay@jaygarcia.com'],
),
fa('it',
_('Systems Administration'),
['cshields@mozilla.com'],
),
fa('research',
_('User Research'),
['jay@jaygarcia.com'],
),
fa('education',
_('Education'),
['bsimon@mozillafoundation.org'],
),
fa('thunderbird',
'Thunderbird',
['jzickerman@mozilla.com', 'rtanglao@mozilla.com'],
),
fa('other',
_('Other'),
['dboswell@mozilla.com'],
),
fa('suggestions',
_('I have a suggestion for Firefox'),
['jay@jaygarcia.com'],
),
fa('issues',
_('I need help with a Firefox issue'),
['jay@jaygarcia.com'],
),
)
INTEREST_CHOICES = (('', _('Area of interest?')),) + tuple(
(area.id, area.name) for area in FUNCTIONAL_AREAS)
FUNCTIONAL_AREAS_DICT = dict((area.id, area) for area in FUNCTIONAL_AREAS)
LOCALE_CONTACTS = {
'bn-BD': ['mahayalamkhan@gmail.com'],
'es-ES': ['nukeador@mozilla-hispano.org'],
'pt-BR': ['marcelo.araldi@yahoo.com.br'],
}
def handle_form(request, form):
if form.is_valid():
data = form.cleaned_data
send(request, data)
autorespond(request, data)
if data['newsletter']:
try:
basket.subscribe(data['email'], 'about-mozilla')
except basket.BasketException:
pass
return True
return False
def send(request, data):
"""Forward contributor's email to our contacts.
All emails are sent to contribute@mozilla.org
For locales with points of contact, it is also sent to them.
For locales without, it is also sent to functional area contacts.
"""
from_ = 'contribute-form@mozilla.org'
subject = 'Inquiry about Mozilla %s' % data['interest']
msg = jingo.render_to_string(request, 'mozorg/emails/infos.txt', data)
headers = {'Reply-To': data['email']}
to = ['contribute@mozilla.org']
cc = None
if request.locale in LOCALE_CONTACTS:
cc = LOCALE_CONTACTS[request.locale]
else:
cc = FUNCTIONAL_AREAS_DICT[data['interest']].contacts
email = EmailMessage(subject, msg, from_, to, cc=cc, headers=headers)
email.send()
def autorespond(request, data):
"""Send an auto-respond email based on chosen field of interest and locale.
You can add localized responses by creating email messages in
mozorg/emails/<category.txt>
"""
functional_area = FUNCTIONAL_AREAS_DICT[data['interest']]
subject = 'Inquiry about Mozilla %s' % data['interest']
to = [data['email']]
from_ = 'contribute-form@mozilla.org'
headers = {}
msg = ''
template = 'mozorg/emails/%s.txt' % functional_area.id
if request.locale != 'en-US' and request.locale in LOCALE_CONTACTS:
template = '%s/templates/%s' % (request.locale, template)
reply_to = LOCALE_CONTACTS[request.locale]
else:
reply_to = functional_area.contacts
try:
msg = jingo.render_to_string(request, template, data)
except TemplateNotFound:
# No template found means no auto-response
return False
# FIXME Why ?
msg = msg.replace('\n', '\r\n')
headers = {'Reply-To': ','.join(reply_to)}
email = EmailMessage(subject, msg, from_, to, headers=headers)
email.send()

Просмотреть файл

@ -3,11 +3,12 @@
from django import forms
from django.forms import widgets
from django.utils.safestring import mark_safe
from django.core.validators import EMPTY_VALUES
from captcha.fields import ReCaptchaField
from l10n_utils.dotlang import _
from product_details import product_details
from .email_contribute import INTEREST_CHOICES
FORMATS = (('H', 'HTML'), ('T', 'Text'))
@ -68,36 +69,16 @@ class NewsletterCountryForm(NewsletterForm):
self.fields['country'] = forms.ChoiceField(choices=regions,
initial=locale)
INTEREST_CHOICES = (('', _('Area of interest?')),
('Support', _('Helping Users')),
('QA', _('Testing and QA')),
('Coding', _('Coding')),
('Marketing', _('Marketing')),
('Localization', _('Localization and Translation')),
('Webdev', _('Web Development')),
('Add-ons', _('Add-ons')),
('Design', _('Visual Design')),
('Students', _('Student Reps')),
('Documentation', _('Developer Documentation')),
('Accessibility', _('Accessibility')),
('IT', _('Systems Administration')),
('Research', _('User Research')),
('Education', 'Education'),
('Thunderbird', _('Thunderbird')),
(' ', _('Other')),
('Firefox Suggestions', _('I have a suggestion for Firefox')),
('Firefox Issue', _('I need help with a Firefox issue')))
class ContributeForm(forms.Form):
email = forms.EmailField(widget=EmailInput(attrs={'required':'true'}))
email = forms.EmailField(widget=EmailInput(attrs={'required': 'true'}))
privacy = forms.BooleanField(widget=PrivacyWidget)
newsletter = forms.BooleanField(required=False)
interest = forms.ChoiceField(
choices=INTEREST_CHOICES,
widget=forms.Select(attrs={'required':'true'}))
widget=forms.Select(attrs={'required': 'true'}))
comments = forms.CharField(
widget=forms.widgets.Textarea(attrs={'required':'true',
'rows':'',
'cols':''}))
captcha = ReCaptchaField(attrs={'theme':'clean'})
widget=forms.widgets.Textarea(attrs={'required': 'true',
'rows': '',
'cols': ''}))
captcha = ReCaptchaField(attrs={'theme': 'clean'})

Просмотреть файл

@ -1,9 +1,9 @@
{% if not success or return_to_form %}
{% if not contribute_success or return_to_form %}
<form class="billboard{% if form.errors %} has-errors{% endif %}"
action="#help-form" id="help-form" method="post">
<input type="hidden" name="contribute-form" value="Y" />
{% if success and return_to_form %}
{% if contribute_success and return_to_form %}
<h4 id="thank-you">
{{ _('Thank you for getting in touch! You will hear from us soon.') }}
</h4>
@ -35,7 +35,7 @@
</ul>
{% endif %}
<div id="form-content"
{% if success and return_to_form %} style="display:none"{% endif %}>
{% if contribute_success and return_to_form %} style="display:none"{% endif %}>
<fieldset>
<div class="row">
<div class="form-column-1">
@ -66,12 +66,14 @@
<div class="field field-privacy {% if form.privacy.errors%}field-error{% endif %}">
{{ form.privacy|safe }}
</div>
<div class="field field-newsletter">
<label for="id_newsletter">
{{ form.newsletter|safe }}
{{_('Id like to receive regular contribution news by email')}}
</label>
</div>
{% if LANG.startswith('en') %}
<div class="field field-newsletter">
<label for="id_newsletter">
{{ form.newsletter|safe }}
{{_('Id like to receive regular contribution news by email')}}
</label>
</div>
{% endif %}
<div class="field field-captcha">
{{ form.captcha }}
</div>

Просмотреть файл

@ -56,7 +56,7 @@
<ul class="pager-tabs">
<li><a href="#interest" class="button-white">{{_('Interest')}}</a></li>
<li><a href="#location" class="button-white">{{_('Location')}}</a></li>
<li><a href="#newsletter" class="button-white">{{_('Newsletter')}}</a></li>
{% if LANG.startswith('en') %}<li><a href="#newsletter" class="button-white">{{_('Newsletter')}}</a></li>{% endif %}
</ul>
</nav>
@ -156,41 +156,43 @@
</div>
<div class="newsletter-content" id="newsletter">
<script>// <![CDATA[
document.getElementById('newsletter').id = 'page-newsletter';
// ]]></script>
<section>
{% if not newsletter_success %}
<form action="#newsletter" method="post" id="newsletter-form">
<input type="hidden" name="newsletter-form" value="Y" />
<p>{{_('Sign up for a weekly newsletter that is full of community news and contribution opportunities.')}}</p>
{% if newsletter_form.errors %}
{{ newsletter_form.non_field_errors()|safe }}
{% if LANG.startswith('en') %}
<div class="newsletter-content" id="newsletter">
<script>// <![CDATA[
document.getElementById('newsletter').id = 'page-newsletter';
// ]]></script>
<section>
{% if not newsletter_success %}
<form action="#newsletter" method="post" id="newsletter-form">
<input type="hidden" name="newsletter-form" value="Y" />
<p>{{_('Sign up for a weekly newsletter that is full of community news and contribution opportunities.')}}</p>
{% if newsletter_form.errors %}
{{ newsletter_form.non_field_errors()|safe }}
<ul class="errorlist">
{% if newsletter_form.email.errors %}
<li>{{_('Please enter a valid email address.')}}</li>
{% endif %}
<ul class="errorlist">
{% if newsletter_form.email.errors %}
<li>{{_('Please enter a valid email address.')}}</li>
{% endif %}
{% if newsletter_form.privacy.errors %}
<li>{{_('You must agree to the privacy policy.')}}</li>
{% endif %}
</ul>
{% endif %}
{% if newsletter_form.privacy.errors %}
<li>{{_('You must agree to the privacy policy.')}}</li>
{% endif %}
</ul>
{% endif %}
<div class="field field-email">{{ field_with_attrs(newsletter_form.email, placeholder=_('YOUR EMAIL HERE'))|safe }}</div>
<div class="field field-country">{{ newsletter_form.country|safe }}</div>
<div class="field field-format">{{ newsletter_form.fmt|safe }}</div>
<div class="field field-privacy">{{ newsletter_form.privacy|safe }}</div>
<div class="field field-submit"><input name="test-submit" type="submit" value="{{_('Subscribe')}}" class="button-blue" /></div>
<small>{{_('We will only send you Mozilla-related information.')}}</small>
</form>
{% else %}
<h4>{{_('Thank you!')}}</h4>
{% endif %}
</section>
</div>
<div class="field field-email">{{ field_with_attrs(newsletter_form.email, placeholder=_('YOUR EMAIL HERE'))|safe }}</div>
<div class="field field-country">{{ newsletter_form.country|safe }}</div>
<div class="field field-format">{{ newsletter_form.fmt|safe }}</div>
<div class="field field-privacy">{{ newsletter_form.privacy|safe }}</div>
<div class="field field-submit"><input name="test-submit" type="submit" value="{{_('Subscribe')}}" class="button-blue" /></div>
<small>{{_('We will only send you Mozilla-related information.')}}</small>
</form>
{% else %}
<h4>{{_('Thank you!')}}</h4>
{% endif %}
</section>
</div>
{% endif %}
</div>

Просмотреть файл

Просмотреть файл

@ -0,0 +1,3 @@
Email: {{ email }}
Area of Interest: {{ interest }}
Comment: {{ comments }}

Просмотреть файл

@ -0,0 +1,15 @@
from django.conf import settings
from mozorg.tests import TestCase
from nose.tools import assert_false, eq_, ok_
from mozorg.email_contribute import FUNCTIONAL_AREAS, LOCALE_CONTACTS
class TestEmailContribute(TestCase):
def test_valid_locale_contacts(self):
for locale, contacts in LOCALE_CONTACTS.items():
ok_(locale in settings.PROD_LANGUAGES)
ok_(type(contacts) is list)
def test_valid_functional_areas(self):
for area in FUNCTIONAL_AREAS:
ok_(type(area.contacts) is list)

Просмотреть файл

@ -0,0 +1,119 @@
from mock import Mock, patch
from captcha.fields import ReCaptchaField
from django.core import mail
from django.test.client import Client
from mozorg.tests import TestCase
from funfactory.urlresolvers import reverse
from nose.tools import assert_false, eq_, ok_
from nose.plugins.skip import SkipTest
from pyquery import PyQuery as pq
class TestContribute(TestCase):
def setUp(self):
self.client = Client()
with self.activate('en-US'):
self.url_en = reverse('mozorg.contribute')
with self.activate('pt-BR'):
self.url_pt_br = reverse('mozorg.contribute')
self.contact = 'foo@bar.com'
self.data = {
'contribute-form': 'Y',
'email': self.contact,
'interest': 'coding',
'privacy': True,
'comments': 'Wesh!',
}
def tearDown(self):
mail.outbox = []
def test_newsletter_en_only(self):
"""Test that the newsletter features are only available in en-US"""
response = self.client.get(self.url_en)
doc = pq(response.content)
ok_(doc('.field-newsletter'))
ok_(doc('a[href="#newsletter"]'))
ok_(doc('#newsletter'))
with self.activate('fr'):
url = reverse('mozorg.contribute')
response = self.client.get(url)
doc = pq(response.content)
assert_false(doc('.field-NEWSLETTER'))
assert_false(doc('a[href="#newsletter"]'))
assert_false(doc('#newsletter'))
@patch.object(ReCaptchaField, 'clean', Mock())
def test_no_autoresponse(self):
"""Test contacts for functional area without autoresponses"""
self.data.update(interest='coding')
self.client.post(self.url_en, self.data)
eq_(len(mail.outbox), 1)
m = mail.outbox[0]
eq_(m.from_email, 'contribute-form@mozilla.org')
eq_(m.to, ['contribute@mozilla.org'])
eq_(m.cc, ['josh@joshmatthews.net'])
eq_(m.extra_headers['Reply-To'], self.contact)
@patch.object(ReCaptchaField, 'clean', Mock())
def test_with_autoresponse(self):
"""Test contacts for functional area with autoresponses"""
self.data.update(interest='support')
self.client.post(self.url_en, self.data)
eq_(len(mail.outbox), 2)
cc = ['jay@jaygarcia.com', 'rardila@mozilla.com', 'madasan@gmail.com']
m = mail.outbox[0]
eq_(m.from_email, 'contribute-form@mozilla.org')
eq_(m.to, ['contribute@mozilla.org'])
eq_(m.cc, cc)
eq_(m.extra_headers['Reply-To'], self.contact)
m = mail.outbox[1]
eq_(m.from_email, 'contribute-form@mozilla.org')
eq_(m.to, [self.contact])
eq_(m.cc, [])
eq_(m.extra_headers['Reply-To'], ','.join(cc))
@patch.object(ReCaptchaField, 'clean', Mock())
def test_no_autoresponse_locale(self):
"""
L10N version to test contacts for functional area without autoresponses
"""
self.data.update(interest='coding')
self.client.post(self.url_pt_br, self.data)
eq_(len(mail.outbox), 1)
m = mail.outbox[0]
eq_(m.from_email, 'contribute-form@mozilla.org')
eq_(m.to, ['contribute@mozilla.org'])
eq_(m.cc, ['marcelo.araldi@yahoo.com.br'])
eq_(m.extra_headers['Reply-To'], self.contact)
@patch.object(ReCaptchaField, 'clean', Mock())
def test_with_autoresponse_locale(self):
"""
L10N version to test contacts for functional area with autoresponses
"""
# UnSkip once pt-BR translation of support.txt is done
raise SkipTest
self.data.update(interest='support')
self.client.post(self.url_pt_br, self.data)
eq_(len(mail.outbox), 2)
cc = ['marcelo.araldi@yahoo.com.br']
m = mail.outbox[0]
eq_(m.from_email, 'contribute-form@mozilla.org')
eq_(m.to, ['contribute@mozilla.org'])
eq_(m.cc, cc)
eq_(m.extra_headers['Reply-To'], self.contact)
m = mail.outbox[1]
eq_(m.from_email, 'contribute-form@mozilla.org')
eq_(m.to, [self.contact])
eq_(m.cc, [])
eq_(m.extra_headers['Reply-To'], ','.join(cc))

Просмотреть файл

@ -1,61 +1,40 @@
from django.core.mail import EmailMessage
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
import jingo
from product_details import product_details
import basket
import l10n_utils
from l10n_utils.dotlang import _
from forms import ContributeForm, NewsletterCountryForm
def handle_contribute_form(request, form):
if form.is_valid():
data = form.cleaned_data
contribute_send(data)
contribute_autorespond(request, data)
if data['newsletter']:
try:
basket.subscribe(data['email'], 'about-mozilla')
except basket.BasketException, e: pass
return True
return False
from mozorg import email_contribute
from mozorg.forms import ContributeForm, NewsletterCountryForm
@csrf_exempt
def contribute(request, template, return_to_form):
def has_contribute_form():
return (request.method == 'POST' and
'contribute-form' in request.POST)
def has_newsletter_form():
return (request.method == 'POST' and
'newsletter-form' in request.POST)
has_contribute_form = (request.method == 'POST' and
'contribute-form' in request.POST)
has_newsletter_form = (request.method == 'POST' and
'newsletter-form' in request.POST)
locale = getattr(request, 'locale', 'en-US')
success = False
contribute_success = False
newsletter_success = False
# This is ugly, but we need to handle two forms. I would love if
# these forms could post to separate pages and get redirected
# back, but we're forced to keep the error/success workflow on the
# same page. Please change this.
if has_contribute_form():
if has_contribute_form:
form = ContributeForm(request.POST)
success = handle_contribute_form(request, form)
if success:
contribute_success = email_contribute.handle_form(request, form)
if contribute_success:
# If form was submitted successfully, return a new, empty
# one.
form = ContributeForm()
else:
form = ContributeForm()
if has_newsletter_form():
if has_newsletter_form:
newsletter_form = NewsletterCountryForm(locale,
request.POST,
prefix='newsletter')
@ -68,7 +47,7 @@ def contribute(request, template, return_to_form):
format=data['fmt'],
country=data['country'])
newsletter_success = True
except basket.BasketException, e:
except basket.BasketException:
msg = newsletter_form.error_class(
['We apologize, but an error occurred in our system.'
'Please try again later.']
@ -80,116 +59,7 @@ def contribute(request, template, return_to_form):
return l10n_utils.render(request,
template,
{'form': form,
'success': success,
'contribute_success': contribute_success,
'newsletter_form': newsletter_form,
'newsletter_success': newsletter_success,
'return_to_form': return_to_form})
def contribute_send(data, locale='en-US'):
"""Forward contributor's email to our contacts.
For localized contacts, add the local contact's email to the
dictionary as in the following example
e.g
CCS = { 'QA': {'all': 'all@example.com', 'el': 'el@example.com'} }
Now all emails for QA get send to 'all@example.com' except
the greek ones which get send to 'el@example.com'.
"""
CCS = {
'QA': _('qa-contribute@mozilla.org'),
'Thunderbird': _('tb-kb@mozilla.com'),
'Research': _('diane+contribute@mozilla.com'),
'Design': _('creative@mozilla.com'),
'Security': _('security@mozilla.com'),
'Docs': _('eshepherd@mozilla.com'),
'Drumbeat': _('drumbeat@mozilla.com'),
'Browser Choice': _('isandu@mozilla.com'),
'IT': _('cshields@mozilla.com'),
'Marketing': _('cnovak@mozilla.com'),
'Add-ons': _('atsay@mozilla.com'),
'Education': _('joinmozilla@mozilla.org'),
}
from_ = 'contribute-form@mozilla.org'
subject = 'Inquiry about Mozilla %s' % data['interest']
msg = ("Email: %s\r\nArea of Interest: %s\r\nComment: %s\r\n"
% (data['email'], data['interest'], data['comments']))
headers = {'Reply-To': data['email']}
# Send email To: contribute@mozilla.org and Cc: a team from the
# CCS list, if applicable. When in DEV mode copy From: to To: and
# don't add Cc:
to = ['contribute@mozilla.org']
if settings.DEV:
to = [data['email']]
cc = None
if not settings.DEV and data['interest'] in CCS:
email_list = CCS[data['interest']]
cc = [email_list.get(locale, email_list['all'])]
email = EmailMessage(subject, msg, from_, to, cc=cc, headers=headers)
email.send()
def contribute_autorespond(request, data, locale='en-US'):
"""Send an auto-respond email based on chosen field of interest and locale.
You can add localized responses by creating email messages in
mozorg/emails/<category.txt>
"""
replies = {
'Support': _('jay@jaygarcia.com'),
'Localization': _('fiotakis@otenet.gr'),
'QA': _('qa-contribute@mozilla.org'),
'Add-ons': _('atsay@mozilla.com'),
'Marketing': _('cnovak@mozilla.com'),
'Design': _('creative@mozilla.com'),
'Documentation': _('jay@jaygarcia.com'),
'Research': _('jay@jaygarcia.com'),
'Thunderbird': _('jzickerman@mozilla.com'),
'Accessibility': _('jay@jaygarcia.com'),
'Firefox Suggestions': _('jay@jaygarcia.com'),
'Firefox Issue': _('dboswell@mozilla.com'),
'Webdev': _('lcrouch@mozilla.com'),
'Education': _('joinmozilla@mozilla.org'),
' ': _('dboswell@mozilla.com')
}
msgs = {
'Support': 'mozorg/emails/support.txt',
'QA': 'mozorg/emails/qa.txt',
'Add-ons': 'mozorg/emails/addons.txt',
'Marketing': 'mozorg/emails/marketing.txt',
'Design': 'mozorg/emails/design.txt',
'Documentation': 'mozorg/emails/documentation.txt',
'Firefox Suggestions': 'mozorg/emails/suggestions.txt',
'Firefox Issue': 'mozorg/emails/issue.txt',
'Webdev': 'mozorg/emails/webdev.txt',
'Education': 'mozorg/emails/education.txt',
' ': 'mozorg/emails/other.txt'
}
subject = 'Inquiry about Mozilla %s' % data['interest']
to = [data['email']]
from_ = 'contribute-form@mozilla.org'
headers = {}
msg = ''
if data['interest'] in msgs:
msg = jingo.render_to_string(request, msgs[data['interest']], data)
else:
return False
msg = msg.replace('\n', '\r\n')
if data['interest'] in replies:
headers = {'Reply-To': replies[data['interest']]}
email = EmailMessage(subject, msg, from_, to, headers=headers)
email.send()

Просмотреть файл

@ -1,7 +1,6 @@
# coding=utf-8
import os
import unittest
from mock import patch
from nose.tools import eq_
@ -9,11 +8,12 @@ from nose.tools import eq_
from django.conf import settings
from django.core import mail
from l10n_utils.dotlang import FORMAT_IDENTIFIER_RE, parse, translate
from mozorg.tests import TestCase
ROOT = os.path.dirname(os.path.abspath(__file__))
class TestDotlang(unittest.TestCase):
class TestDotlang(TestCase):
def test_parse(self):
path = os.path.join(ROOT, 'test.lang')
parsed = parse(path)
@ -49,7 +49,8 @@ class TestDotlang(unittest.TestCase):
def test_format_identifier_mismatch(self):
path = 'format_identifier_mismatch'
expected = '%(foo)s is the new %s'
result = translate(expected, [path])
with self.activate('en-US'):
result = translate(expected, [path])
eq_(expected, result)
eq_(len(mail.outbox), 1)
eq_(mail.outbox[0].subject, '[Django] %s is corrupted' % path)

Просмотреть файл

@ -454,3 +454,11 @@ RECAPTCHA_PUBLIC_KEY = ''
RECAPTCHA_PRIVATE_KEY = ''
TEST_RUNNER = 'test_utils.runner.NoDBTestSuiterunner'
def lazy_email_backend():
from django.conf import settings
return ('django.core.mail.backends.console.EmailBackend' if settings.DEV
else 'django.core.mail.backends.smtp.EmailBackend')
EMAIL_BACKEND = lazy(lazy_email_backend, str)()