├── .dockerignore ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .landscape.yaml ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── TODO.RU ├── Vagrantfile ├── __init__.py ├── dbmail ├── __init__.py ├── admin.py ├── apps.py ├── backends │ ├── __init__.py │ ├── bot.py │ ├── mail.py │ ├── push.py │ ├── sms.py │ └── tts.py ├── defaults.py ├── exceptions.py ├── fields.py ├── fixtures │ ├── mailgun_base_templates.json │ └── mailgun_base_templates.md ├── locale │ └── ru │ │ └── LC_MESSAGES │ │ ├── django.mo │ │ └── django.po ├── management │ ├── __init__.py │ └── commands │ │ ├── __init__.py │ │ ├── clean_dbmail_cache.py │ │ ├── clean_dbmail_logs.py │ │ ├── dbmail_test_send.py │ │ ├── load_dbmail_base_templates.py │ │ ├── send_dbmail_deferred_signal.py │ │ └── update_dbmail_cache.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20150321_1539.py │ ├── 0003_maillog_backend.py │ ├── 0004_auto_20150321_2214.py │ ├── 0005_auto_20150506_2201.py │ ├── 0006_auto_20150708_0714.py │ ├── 0007_auto_20150708_2016.py │ ├── 0008_auto_20151007_1918.py │ ├── 0009_auto_20160311_0918.py │ ├── 0010_auto_20160728_1645.py │ ├── 0011_auto_20160803_1024.py │ ├── 0012_auto_20160915_1340.py │ ├── 0013_auto_20160923_2201.py │ ├── 0014_auto_20170801_1206.py │ ├── 0015_auto_20180926_1206.py │ └── __init__.py ├── models.py ├── providers │ ├── __init__.py │ ├── apple │ │ ├── __init__.py │ │ ├── apns.py │ │ ├── apns2.py │ │ └── errors.py │ ├── boxcar │ │ ├── __init__.py │ │ └── push.py │ ├── centrifugo │ │ ├── __init__.py │ │ └── push.py │ ├── google │ │ ├── __init__.py │ │ ├── android.py │ │ └── browser.py │ ├── http │ │ ├── __init__.py │ │ └── push.py │ ├── iqsms │ │ ├── __init__.py │ │ └── sms.py │ ├── microsoft │ │ ├── __init__.py │ │ ├── base.py │ │ ├── raw.py │ │ ├── tile.py │ │ └── toast.py │ ├── nexmo │ │ ├── __init__.py │ │ ├── sms.py │ │ └── tts.py │ ├── parse_com │ │ ├── __init__.py │ │ └── push.py │ ├── prowl │ │ ├── __init__.py │ │ └── push.py │ ├── pubnub │ │ ├── __init__.py │ │ └── push.py │ ├── pushall │ │ ├── __init__.py │ │ └── push.py │ ├── pushover │ │ ├── __init__.py │ │ └── push.py │ ├── sendinblue │ │ ├── __init__.py │ │ └── mail.py │ ├── slack │ │ ├── __init__.py │ │ └── push.py │ ├── smsaero │ │ ├── __init__.py │ │ └── sms.py │ ├── smsbliss │ │ ├── __init__.py │ │ └── sms.py │ ├── telegram │ │ ├── __init__.py │ │ └── bot.py │ └── twilio │ │ ├── __init__.py │ │ └── sms.py ├── signals.py ├── static │ └── dbmail │ │ └── admin │ │ └── js │ │ └── dbmail.js ├── tasks.py ├── templates │ └── dbmail │ │ ├── admin │ │ └── change_list_link.html │ │ ├── apps.html │ │ └── browse.html ├── test_cases.py ├── translation.py ├── urls.py ├── utils.py └── views.py ├── demo ├── .vagrant_bootstrap.sh ├── api.data.txt ├── auth.json ├── demo │ ├── __init__.py │ ├── custom_backends │ │ ├── __init__.py │ │ ├── double.py │ │ └── slack.py │ ├── dashboard.py │ ├── settings.py │ ├── static │ │ ├── images │ │ │ └── icon-256x256.png │ │ ├── js │ │ │ └── service-worker.js │ │ └── manifest.json │ ├── templates │ │ ├── 404.html │ │ ├── 500.html │ │ ├── browser_notification.html │ │ └── web-push.html │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── manage.py └── requirements.txt ├── docs ├── Makefile ├── __init__.py ├── api.rst ├── authors.rst ├── commands.rst ├── conf.py ├── conf.rst ├── contributing.rst ├── custom_backends_and_providers.rst ├── demo.rst ├── dev_install.rst ├── index.rst ├── installation.rst ├── providers.rst ├── settings.rst ├── signals.rst ├── usage.rst └── web_push.rst ├── requirements ├── docs.txt ├── package.txt └── tests.txt ├── screenshots ├── apikey_changelist.jpg ├── apps_browse_vars.jpg ├── apps_view.jpg ├── base_template_changelist.jpg ├── bcc_changelist.jpg ├── group_change.jpg ├── signal_edit.jpg ├── signals_changelist.jpg ├── smtp_changelist.jpg ├── subscriptions_change.jpg ├── subscriptions_changelist.jpg ├── template_compare.jpg ├── template_edit.jpg ├── template_log_changelist.jpg ├── template_log_view.jpg ├── templates_changelist.jpg └── tracking_edit.jpg ├── setup.cfg ├── setup.py └── tox.ini /.dockerignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | .tox 3 | *.egg-info 4 | .coverage 5 | screenshots 6 | .idea 7 | .git 8 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | **Do you want to request a *feature* or report a *bug*?** 2 | 3 | **What is the current behavior?** 4 | 5 | **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem** 6 | 7 | **Full traceback** 8 | 9 | **What is the expected behavior?** 10 | 11 | **Which versions of DbMail, Django and which Python / OS are affected by this issue? Did this work in previous versions of DbMail?** 12 | 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Before to submit your pull-request: 2 | 3 | - Be sure that the unit tests pass 4 | - If you create a new feature, test your code, do not introduce new bugs 5 | - Avoid code duplication 6 | - Small pull-requests are easier to review and can be merged quickly 7 | - 1 pull-request == 1 feature/improvement 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.sqlite 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Packages 9 | *.egg 10 | *.egg-info 11 | dist 12 | build 13 | eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | develop-eggs 19 | .installed.cfg 20 | lib 21 | lib64 22 | *.patch 23 | 24 | # Installer logs 25 | pip-log.txt 26 | 27 | # Unit test / coverage reports 28 | .coverage 29 | .tox 30 | nosetests.xml 31 | 32 | # Translations 33 | *.mo 34 | *.log 35 | *.current 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # Complexity 43 | output/*.html 44 | output/*/index.html 45 | 46 | # Sphinx 47 | docs/_build 48 | docs/.build 49 | TODO 50 | local_settings.py 51 | mail_files 52 | .vagrant 53 | certs/* 54 | demo/*.zip 55 | -------------------------------------------------------------------------------- /.landscape.yaml: -------------------------------------------------------------------------------- 1 | doc-warnings: no 2 | test-warnings: no 3 | strictness: high 4 | max-line-length: 79 5 | uses: 6 | - django 7 | autodetect: yes 8 | ignore-paths: 9 | - dbmail/migrations 10 | - dbmail/management 11 | - demo 12 | 13 | 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.4" 5 | - "3.5" 6 | 7 | before_install: 8 | - export PIP_USE_MIRRORS=true 9 | install: 10 | - pip install -e . 11 | - pip install -r requirements/tests.txt Django==$DJANGO 12 | before_script: 13 | - flake8 --ignore=E402,E731,F401,F811 --exclude=migrations dbmail 14 | script: 15 | - make test 16 | - make coverage 17 | env: 18 | - DJANGO=2.1 19 | - DJANGO=2.0 20 | - DJANGO=1.8 21 | - DJANGO=1.11 22 | matrix: 23 | # https://docs.djangoproject.com/en/dev/faq/install/#what-python-version-can-i-use-with-django 24 | include: 25 | - { python: "3.6", env: DJANGO=1.11 } 26 | - { python: "3.6", env: DJANGO=2.0 } 27 | - { python: "3.6", env: DJANGO=2.1 } 28 | 29 | - { python: "3.7", env: DJANGO=2.0, dist: xenial, sudo: true } 30 | - { python: "3.7", env: DJANGO=2.1, dist: xenial, sudo: true } 31 | exclude: 32 | - python: "2.7" 33 | env: "DJANGO=2.0" 34 | 35 | - python: "2.7" 36 | env: "DJANGO=2.1" 37 | 38 | - python: "3.4" 39 | env: "DJANGO=2.1" 40 | 41 | branches: 42 | only: 43 | - master 44 | - development 45 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * GoTLiuM InSPiRiT (`gotlium `_) 9 | 10 | Contributors 11 | ------------ 12 | 13 | * Sergey Nikitin (`nikitinsm `_) 14 | * Volodymyr Tartynskyi (`vosi `_) 15 | * Petr Zelenin (`petrikoz `_) 16 | * icegreg (`icegreg `_) 17 | * Andrei (`aloskutov-ki11 `_) 18 | * Sergey Khaylov (`skhaylov `_) 19 | * Ilya Streltsov (`ilstreltsov `_) 20 | * Antoine Nguyen (`tonioo `_) 21 | * Tomáš Peterka (`katomaso `_) 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/LPgenerator/django-db-mailer/issues. 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | is open to whoever wants to implement it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "feature" 34 | is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | django-db-mailer could always use more documentation, whether as part of the 40 | official django-db-mailer docs, in docstrings, or even on the web in blog posts, 41 | articles, and such. 42 | 43 | Submit Feedback 44 | ~~~~~~~~~~~~~~~ 45 | 46 | The best way to send feedback is to file an issue at https://github.com/LPgenerator/django-db-mailer/issues. 47 | 48 | If you are proposing a feature: 49 | 50 | * Explain in detail how it would work. 51 | * Keep the scope as narrow as possible, to make it easier to implement. 52 | * Remember that this is a volunteer-driven project, and that contributions 53 | are welcome :) 54 | 55 | Get Started! 56 | ------------ 57 | 58 | Ready to contribute? Here's how to set up `django-db-mailer` for local development. 59 | 60 | 1. Fork the `django-db-mailer` repo on GitHub. 61 | 2. Clone your fork locally and switch to development branch:: 62 | 63 | $ git clone git@github.com:LPgenerator/django-db-mailer.git 64 | $ cd django-db-mailer/ 65 | $ git fetch --all 66 | $ git checkout development 67 | 68 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 69 | 70 | $ mkvirtualenv django-db-mailer 71 | $ workon django-db-mailer 72 | $ python setup.py develop 73 | 74 | 4. Create a branch for local development:: 75 | 76 | $ git checkout -b name-of-your-bugfix-or-feature 77 | 78 | Now you can make your changes locally. 79 | 80 | 5. When you're done making changes, check that your changes pass flake8 and the 81 | tests, including testing other Python versions with tox:: 82 | 83 | $ make pep8 84 | $ make test 85 | $ tox 86 | 87 | To get flake8 and tox, just pip install them into your virtualenv. 88 | 89 | 6. Commit your changes and push your branch to GitHub:: 90 | 91 | $ git add . 92 | $ git commit -m "Your detailed description of your changes." 93 | $ git push origin name-of-your-bugfix-or-feature 94 | 95 | 7. Submit a pull request through the GitHub website. 96 | 97 | Pull Request Guidelines 98 | ----------------------- 99 | 100 | Before you submit a pull request, check that it meets these guidelines: 101 | 102 | 1. The pull request should include tests. 103 | 2. If the pull request adds functionality, the docs should be updated. Put 104 | your new functionality into a function with a docstring, and add the 105 | feature to the list in README.rst. 106 | 3. The pull request should work with Python 2.7, 3.5+, PyPy. Check 107 | https://travis-ci.org/LPgenerator/django-db-mailer/pull_requests 108 | and make sure that the tests pass for all supported Python versions. 109 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | MAINTAINER gotlium 3 | 4 | RUN apt-get update && apt-get install -y redis-server git \ 5 | python3 python3-pip python3-dev libxml2-dev libxslt-dev zlib1g-dev && \ 6 | apt-get clean 7 | 8 | # http://bugs.python.org/issue19846 9 | # > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK. 10 | ENV LANG C.UTF-8 11 | 12 | RUN mkdir /mailer 13 | ADD ./demo/requirements.txt /mailer/requirements.txt 14 | RUN pip3 install --upgrade pip 15 | RUN pip3 install -r /mailer/requirements.txt 16 | 17 | ADD ./demo/ /mailer 18 | 19 | RUN python3 /mailer/manage.py migrate --noinput 20 | RUN python3 /mailer/manage.py loaddata /mailer/auth.json 21 | 22 | WORKDIR /mailer 23 | 24 | CMD /bin/bash -c 'C_FORCE_ROOT=1 python3 /mailer/manage.py celeryd -Q default >& /dev/null & redis-server >& /dev/null & python3 /mailer/manage.py runserver 0.0.0.0:8000' 25 | 26 | EXPOSE 8000 27 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE 3 | include AUTHORS.rst 4 | include CONTRIBUTING.rst 5 | include requirements/* 6 | recursive-exclude * .DS_Store 7 | recursive-include dbmail *.html *.js *.py *.po *.mo *.json 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | # target: clean - Clean temporary files 3 | clean: clean-build clean-pyc 4 | 5 | clear: clean 6 | @true 7 | 8 | clean-build: 9 | @rm -fr build/ 10 | @rm -fr dist/ 11 | @rm -fr *.egg-info 12 | 13 | clean-pyc: 14 | @find . -name '*.pyc' -exec rm -f {} + 15 | @find . -name '*.pyo' -exec rm -f {} + 16 | @find . -name '*~' -exec rm -f {} + 17 | 18 | .PHONY: clean-celery 19 | # target: clean-celery - Clean all celery queues 20 | clean-celery: 21 | @cd demo && python manage.py celery purge -f 22 | 23 | .PHONY: pep8 24 | # target: pep8 - Check code for pep8 rules 25 | pep8: 26 | @flake8 dbmail --ignore=E402,E731,F401,F401 --exclude=migrations 27 | 28 | .PHONY: release 29 | # target: release - Release app into PyPi 30 | release: clean 31 | @python setup.py register sdist upload --sign 32 | @python setup.py bdist_wheel upload --sign 33 | 34 | .PHONY: sphinx 35 | # target: sphinx - Make app docs 36 | sphinx: 37 | @rm -rf ./docs/.build/html/ 38 | @cd docs && sphinx-build -b html -d .build/doctrees . .build/html 39 | @xdg-open docs/.build/html/index.html >& /dev/null || open docs/.build/html/index.html >& /dev/null || true 40 | 41 | sdist: clean 42 | @python setup.py sdist 43 | @ls -l dist 44 | 45 | .PHONY: run 46 | # target: run - Run Django development server 47 | run: kill_server 48 | @cd demo && python manage.py runserver --traceback 49 | 50 | run-server: run 51 | 52 | .PHONY: run-uwsgi-server 53 | # target: run-uwsgi-server - Run uWSGI server 54 | run-uwsgi-server: 55 | @cd demo && uwsgi --http-socket 127.0.0.1:9000 --master --processes 8 \ 56 | --home ~/.virtualenvs/django-db-mailer --wsgi demo.wsgi \ 57 | --no-orphans --vacuum --http-keepalive \ 58 | --optimize 2 --buffer-size 65536 --post-buffering 32768 \ 59 | --cpu-affinity 1 --max-requests 10000 \ 60 | --limit-as 1024 --listen 1024 \ 61 | --enable-threads --threads 8 --thunder-lock --disable-logging 62 | 63 | .PHONY: run-celery 64 | # target: run-celery - Run celery daemon 65 | run-celery: 66 | @cd demo && python manage.py celeryd -E -Q default -l INFO -c 8 --traceback -v 3 67 | 68 | .PHONY: run-ab 69 | # target: run-ab - Run apache bench 70 | run-ab: 71 | @ab -k -t 10 -p demo/api.data.txt -c 100 -n 1000 http://127.0.0.1:9000/dbmail/api/ 72 | 73 | .PHONY: shell 74 | # target: shell - Run project shell 75 | shell: 76 | @cd demo && ./manage.py shell_plus --print-sql || ./manage.py shell 77 | 78 | .PHONY: run-redis 79 | # target: run-redis - Run Redis daemon 80 | run-redis: 81 | @redis-server >& /dev/null & 82 | 83 | .PHONY: test 84 | # target: test - Run tests 85 | test: 86 | @cd demo && ./manage.py test dbmail 87 | 88 | .PHONY: tox 89 | # target: tox - Run tests under tox 90 | tox: 91 | @unset PYTHONPATH && tox 92 | 93 | .PHONY: coverage 94 | # target: coverage - Run tests with coverage 95 | coverage: 96 | @cd demo && \ 97 | coverage run --branch --source=dbmail ./manage.py test dbmail && \ 98 | coverage report --omit="*/dbmail/test*,*/dbmail/migrations/*,*/dbmail/admin*" 99 | 100 | .PHONY: help 101 | # target: help - Display callable targets 102 | help: 103 | @egrep "^# target:" [Mm]akefile | sed -e 's/^# target: //g' 104 | 105 | kill_server: 106 | @ps aux|grep [r]unserver|awk '{print $2}'|xargs kill -9 >& /dev/null; true 107 | @ps aux|grep [r]unsslserver|awk '{print $2}'|xargs kill -9 >& /dev/null; true 108 | -------------------------------------------------------------------------------- /TODO.RU: -------------------------------------------------------------------------------- 1 | Version: 2.5 2 | ------------ 3 | * [-] Выбор типа сообщения в сигналах. Может быть: почта, смс, tts, push 4 | * [-] В логах писать какое приложение из внешнего апи создало запрос 5 | * [-] Пофиксить для push уведомлений и смс - bcc, который будет уходить на почту. Так же подумать про cc. 6 | * [-] Тесты + coverage 7 | * [-] Расширить логи. Добавить поле От, добавить текущий язык, добавить: затраченное время на отправку, IP, и поле с request.META. Если передали request в функцию, то записываем значения. 8 | * [-] send_db_many (background task) 9 | * [-] Не отправлять повторно письмо, если в MailLogException будет отмечено игнорирование ошибки 10 | * [+] Ручной сброс кеша из админ панели 11 | * [-] Флаг в настройках для распаковки модели до/после celery 12 | * [-] Совмещение с батарейкой django-protector 13 | * [-] Возможность использовать обычные html шаблоны с фс 14 | 15 | 16 | Version: 2.6 17 | ------------ 18 | * [-] Отделить таски от Celery 19 | * [-] Поддержка uWSGI Spooler 20 | * [-] Поддержка django-rq 21 | * [-] Добавить приоритеты на уровне воркеров (воркеры с именами: Low, High, Medium). Далее роутить сообщение в нужный воркер 22 | * [-] Поправить документацию с учетом 3 пунктов выше 23 | * [-] Скрывать поля приоритета и тд, в случае когда выбранные инструменты не поддерживают Приоритет 24 | 25 | Version: 2.7 26 | ------------ 27 | * [-] Web-сокеты для нотификаций в браузере (Centrifuge) + JS фильтры 28 | * [-] Показывать статистику отправленных и отображать в админке рядом с шаблоном 29 | * [-] Возможность отправки POST файлов в API 30 | 31 | 32 | Version: 2.8 33 | ------------ 34 | * [-] Модели должны обязательно иметь префикс Mail 35 | * [-] Полная стабилизация кодовой базы 36 | 37 | 38 | Version: 3.0 39 | ------------ 40 | * [-] Необходимо пофиксить import_module, заменив на import_by_string. Мысли тут https://github.com/LPgenerator/django-db-mailer/pull/16 41 | * [-] Удалить флаг is_admin. И так есть категории 42 | 43 | Backlog: 44 | -------- 45 | * [-] Возможность принимать звонки, для бекенда twillio https://www.twilio.com/blog/2014/10/making-and-receiving-phone-calls-with-golang.html 46 | * [-] Добавить сигналы на инвалидацию шаблонов, если был использован массовый апдейт или же delete 47 | * [-] Добавить валидацию к сигналам. Должна быть выбранна либо группа, либо указаны правила 48 | * [-] Добавить возможность повторять отправку письма через некоторое время. Типа галочки repeat 49 | * [-] Добавить возможность шифрования данных при отправке конфеденциальной информации 50 | * [-] Запретить удаление в инлайнах в логах писем 51 | * [-] Добавить возможность отписки на основе категорий. Когда пользователь может отписаться от конкретной категории или писем в данной категории. 52 | * [-] Добавить криптографию + таблицу с публичным ключами пользователей 53 | * [-] Центр для массовых рассылок 54 | 55 | * [-] Подсказки по полям 56 | * [-] Графики по логам (фильтры по типам: mail/sms/tts/push) 57 | * [-] Английская версия статьи на хабре для reddit.com 58 | * [-] Антиспам фильтр 59 | * [-] Деплой конфигурация, с uwsgi, mariadb и со всякими оптимизациями (на Ansible) 60 | * [-] Документация с лушими практиками по использованию батарейки 61 | * [-] Полноценная обертка для boxcar, для работы с девайсами iOS/Android/Windows 62 | 63 | * [-] Интеграция с django-blockedemails на основе http://www.block-disposable-email.com/ 64 | * [-] Интеграция с django-email-analytics для Google Analytics 65 | * [-] Интеграция с django-email-extras или django-emailpgp или django-gnupg-mails, для подддержки PGP шифрования 66 | * [-] Прозрачная интеграция с django-email-login или :django-email-login-fc или django_email_auth или django-dual-authentication, для автоматической авторизации пользователя 67 | * [-] django-emailmgr - An email manager for Django user 68 | * [-] django-emailsys - Provide the ability to send emails/sms using WebServices 69 | * [-] django-inbound-email - для обратной интеграции (A Django app for receiving inbound emails) 70 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | 3 | # Options {{{ 4 | # 5 | APP_VM_NAME = "dbmail" 6 | APP_MEMORY = "1024" 7 | APP_CPUS = "2" 8 | # 9 | # }}} 10 | 11 | # Vagrant 2.0.x {{{ 12 | # 13 | Vagrant.configure("2") do |config| 14 | 15 | config.vm.box = "ubuntu/xenial64" 16 | config.vm.post_up_message = "Box URL is http://127.0.0.1:8000/admin/" 17 | 18 | config.vm.synced_folder "./demo/", "/mailer", id: "vagrant-root" 19 | 20 | config.vm.provision :shell, :path => "demo/.vagrant_bootstrap.sh" 21 | config.ssh.shell = "bash -c 'BASH_ENV=/etc/profile exec bash'" 22 | 23 | # Set hostname 24 | config.vm.hostname = APP_VM_NAME 25 | 26 | # Configure network 27 | config.vm.network :forwarded_port, guest: 8000, host: 8000 28 | 29 | # SSH forward 30 | config.ssh.forward_agent = true 31 | 32 | # VirtualBox configuration 33 | config.vm.provider :virtualbox do |vb| 34 | vb.gui = false 35 | vb.name = APP_VM_NAME 36 | vb.customize ["modifyvm", :id, "--memory", APP_MEMORY] 37 | vb.customize ["modifyvm", :id, "--name", APP_VM_NAME] 38 | vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"] 39 | vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"] 40 | vb.customize ["modifyvm", :id, "--ioapic", "on"] 41 | vb.customize ["modifyvm", :id, "--cpus", APP_CPUS] 42 | end 43 | 44 | # LXC configuration 45 | config.vm.provider :lxc do |lxc, override| 46 | override.vm.box = "fgrehm/xenial64-lxc" 47 | end 48 | 49 | if Vagrant.has_plugin?("vagrant-cachier") 50 | config.cache.scope = :box 51 | config.vm.network :private_network, ip: "44.44.44.45" 52 | 53 | config.cache.synced_folder_opts = { 54 | type: :nfs, 55 | mount_options: ['rw', 'vers=3', 'tcp', 'nolock'] 56 | } 57 | end 58 | end 59 | # 60 | # }}} 61 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/__init__.py -------------------------------------------------------------------------------- /dbmail/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | import sys 5 | 6 | 7 | VERSION = (2, 4, 0) 8 | 9 | default_app_config = 'dbmail.apps.DBMailConfig' 10 | 11 | 12 | def get_version(): 13 | return '.'.join(map(str, VERSION)) 14 | 15 | 16 | def app_installed(app): 17 | from django.conf import settings 18 | 19 | return app in settings.INSTALLED_APPS 20 | 21 | 22 | def celery_supported(): 23 | try: 24 | import celery 25 | 26 | return True 27 | except ImportError: 28 | return False 29 | 30 | 31 | def db_sender(slug, recipient, *args, **kwargs): 32 | from dbmail.defaults import ( 33 | CELERY_QUEUE, SEND_MAX_TIME, ENABLE_CELERY, BACKEND, DEBUG) 34 | from dbmail.models import MailTemplate 35 | 36 | args = (slug, recipient) + args 37 | send_after = kwargs.pop('send_after', None) 38 | send_at_date = kwargs.pop('send_at_date', None) 39 | _use_celery = kwargs.pop('use_celery', ENABLE_CELERY) 40 | use_celery = ENABLE_CELERY and _use_celery 41 | backend = kwargs.get('backend', BACKEND['mail']) 42 | 43 | if celery_supported() and use_celery is True: 44 | import dbmail.tasks 45 | 46 | template = MailTemplate.get_template(slug=slug) 47 | max_retries = kwargs.get('max_retries', None) 48 | send_after = send_after if send_after else template.interval 49 | if max_retries is None and template.num_of_retries: 50 | kwargs['max_retries'] = template.num_of_retries 51 | 52 | options = { 53 | 'args': args, 'kwargs': kwargs, 54 | 'queue': kwargs.pop('queue', CELERY_QUEUE), 55 | 'time_limit': kwargs.get('time_limit', SEND_MAX_TIME), 56 | 'priority': template.priority, 57 | } 58 | 59 | if send_at_date is not None and isinstance(send_at_date, datetime): 60 | options.update({'eta': send_at_date}) 61 | if send_after is not None: 62 | options.update({'countdown': send_after}) 63 | if template.is_active: 64 | return dbmail.tasks.db_sender.apply_async(**options) 65 | else: 66 | module = import_module(backend) 67 | if DEBUG is True: 68 | return module.SenderDebug(*args, **kwargs).send(is_celery=False) 69 | return module.Sender(*args, **kwargs).send(is_celery=False) 70 | 71 | 72 | def send_db_mail(*args, **kwargs): 73 | from dbmail.defaults import BACKEND 74 | 75 | kwargs['backend'] = kwargs.pop('backend', BACKEND['mail']) 76 | return db_sender(*args, **kwargs) 77 | 78 | 79 | def send_db_sms(*args, **kwargs): 80 | from dbmail.defaults import BACKEND, SMS_QUEUE 81 | 82 | kwargs['backend'] = kwargs.pop('backend', BACKEND['sms']) 83 | kwargs['queue'] = kwargs.pop('queue', SMS_QUEUE) 84 | return db_sender(*args, **kwargs) 85 | 86 | 87 | def send_db_tts(*args, **kwargs): 88 | from dbmail.defaults import BACKEND, TTS_QUEUE 89 | 90 | kwargs['backend'] = kwargs.pop('backend', BACKEND['tts']) 91 | kwargs['queue'] = kwargs.pop('queue', TTS_QUEUE) 92 | return db_sender(*args, **kwargs) 93 | 94 | 95 | def send_db_push(*args, **kwargs): 96 | from dbmail.defaults import BACKEND, PUSH_QUEUE 97 | 98 | kwargs['backend'] = kwargs.pop('backend', BACKEND['push']) 99 | kwargs['queue'] = kwargs.pop('queue', PUSH_QUEUE) 100 | return db_sender(*args, **kwargs) 101 | 102 | 103 | def send_db_bot(*args, **kwargs): 104 | from dbmail.defaults import BACKEND, BOT_QUEUE 105 | 106 | kwargs['backend'] = kwargs.pop('backend', BACKEND['bot']) 107 | kwargs['queue'] = kwargs.pop('queue', BOT_QUEUE) 108 | return db_sender(*args, **kwargs) 109 | 110 | 111 | def send_db_subscription(*args, **kwargs): 112 | from dbmail.defaults import ( 113 | SUBSCRIPTION_QUEUE, SEND_MAX_TIME, 114 | ENABLE_CELERY, MAIL_SUBSCRIPTION_MODEL) 115 | 116 | MailSubscription = import_by_string(MAIL_SUBSCRIPTION_MODEL) 117 | 118 | use_celery = ENABLE_CELERY and kwargs.pop('use_celery', ENABLE_CELERY) 119 | options = { 120 | 'time_limit': kwargs.pop('time_limit', SEND_MAX_TIME), 121 | 'queue': kwargs.pop('queue', SUBSCRIPTION_QUEUE), 122 | 'args': args, 'kwargs': kwargs, 123 | } 124 | 125 | if celery_supported() and use_celery is True: 126 | from dbmail.tasks import db_subscription 127 | 128 | return db_subscription.apply_async(**options) 129 | else: 130 | kwargs['use_celery'] = use_celery 131 | return MailSubscription.notify(*args, **kwargs) 132 | 133 | 134 | def initial_signals(): 135 | from django.db.utils import DatabaseError, IntegrityError 136 | 137 | for cmd in ['schemamigration', 'migrate', 138 | 'test', 'createsuperuser', 'makemigrations', 139 | 'collectstatic', 'compilemessages']: 140 | if cmd in sys.argv: 141 | break 142 | else: 143 | try: 144 | from dbmail.signals import initial_signals as init_signals 145 | 146 | init_signals() 147 | except (ImportError, DatabaseError, IntegrityError): 148 | pass 149 | 150 | 151 | ## 152 | # Compatibility section 153 | ## 154 | 155 | PY2 = sys.version_info[0] == 2 156 | PY3 = sys.version_info[0] == 3 157 | 158 | 159 | def python_2_unicode_compatible(klass): 160 | """ 161 | A decorator that defines __unicode__ and __str__ methods under Python 2. 162 | Under Python 3 it does nothing. 163 | To support Python 2 and 3 with a single code base, define a __str__ method 164 | returning text and apply this decorator to the class. 165 | """ 166 | if PY2: 167 | if '__str__' not in klass.__dict__: 168 | raise ValueError("@python_2_unicode_compatible cannot be applied " 169 | "to %s because it doesn't define __str__()." % 170 | klass.__name__) 171 | klass.__unicode__ = klass.__str__ 172 | klass.__str__ = lambda self: self.__unicode__().encode('utf-8') 173 | return klass 174 | 175 | 176 | def import_by_string(dotted_path): 177 | """Import class by his full module path. 178 | 179 | Args: 180 | dotted_path - string, full import path for class. 181 | 182 | """ 183 | from django.utils.module_loading import import_string 184 | 185 | return import_string(dotted_path) 186 | 187 | 188 | def import_module(*args, **kwargs): 189 | try: 190 | from django.utils.importlib import import_module 191 | except ImportError: 192 | from importlib import import_module 193 | return import_module(*args, **kwargs) 194 | 195 | 196 | def get_model(*args, **kwargs): 197 | from django.apps import apps 198 | 199 | return apps.get_model(*args, **kwargs) 200 | -------------------------------------------------------------------------------- /dbmail/apps.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from django.utils.translation import ugettext_lazy as _ 4 | from django.apps import AppConfig 5 | 6 | 7 | class DBMailConfig(AppConfig): 8 | name = 'dbmail' 9 | verbose_name = _('DB Mailer') 10 | 11 | def ready(self): 12 | from dbmail import initial_signals 13 | from dbmail.defaults import ALLOWED_MODELS_ON_ADMIN 14 | 15 | if 'Signal' in ALLOWED_MODELS_ON_ADMIN: 16 | initial_signals() 17 | -------------------------------------------------------------------------------- /dbmail/backends/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/backends/bot.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from dbmail.defaults import BOT_PROVIDER, DEFAULT_BOT_FROM 4 | from dbmail.backends.sms import ( 5 | Sender as SenderBase, 6 | SenderDebug as SenderDebugBase 7 | ) 8 | from dbmail.utils import clean_html 9 | from dbmail import import_module 10 | 11 | 12 | class Sender(SenderBase): 13 | provider = BOT_PROVIDER 14 | default_from = DEFAULT_BOT_FROM 15 | 16 | def _get_recipient_list(self, recipient): 17 | return self._email_to_list(recipient) 18 | 19 | def _send(self): 20 | module = import_module(self._provider) 21 | message = clean_html(self._message) 22 | for _id in self._recipient_list: 23 | module.send(_id, message, _from=self._from_email, **self._kwargs) 24 | 25 | 26 | class SenderDebug(SenderDebugBase): 27 | pass 28 | -------------------------------------------------------------------------------- /dbmail/backends/push.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from dbmail.defaults import DEFAULT_PUSH_FROM, PUSH_PROVIDER 4 | from dbmail.backends.sms import Sender as SenderBase 5 | from dbmail.utils import clean_html 6 | from dbmail import import_module 7 | 8 | 9 | class Sender(SenderBase): 10 | provider = PUSH_PROVIDER 11 | default_from = DEFAULT_PUSH_FROM 12 | 13 | def _get_recipient_list(self, recipient): 14 | if not isinstance(recipient, list): 15 | email_list = self._group_emails(recipient) 16 | if email_list: 17 | return email_list 18 | return self._email_to_list(recipient) 19 | 20 | def _send(self): 21 | message = clean_html(self._message) 22 | 23 | options = self._kwargs.copy() 24 | options.update( 25 | message=message, 26 | event=self._kwargs.pop('event', self._subject) 27 | ) 28 | if self._from_email: 29 | options['app'] = self._from_email 30 | 31 | module = import_module(self._provider) 32 | 33 | for address in self._recipient_list: 34 | module.send(address, **options) 35 | 36 | 37 | class SenderDebug(Sender): 38 | def _send(self): 39 | self.debug('Provider', self._provider or self.provider) 40 | self.debug('Message', clean_html(self._message)) 41 | self.debug('Recipients', self._recipient_list) 42 | self.debug('Event', self._subject) 43 | self.debug('App', self._from_email) 44 | -------------------------------------------------------------------------------- /dbmail/backends/sms.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from django.conf import settings 4 | 5 | from dbmail.defaults import SMS_PROVIDER, DEFAULT_SMS_FROM 6 | from dbmail.backends.mail import Sender as SenderBase 7 | from dbmail.utils import clean_html 8 | from dbmail import import_module 9 | 10 | 11 | class Sender(SenderBase): 12 | provider = SMS_PROVIDER 13 | default_from = DEFAULT_SMS_FROM 14 | 15 | def _get_from_email(self): 16 | if self._kwargs.get('from_email'): 17 | return self._kwargs.pop('from_email', None) 18 | elif not self._template.from_email: 19 | if self.default_from: 20 | return self.default_from 21 | else: 22 | return settings.DEFAULT_FROM_EMAIL.split('<')[0].strip() 23 | return self._template.from_email.email 24 | 25 | def _get_recipient_list(self, recipient): 26 | if not isinstance(recipient, list) and '+' not in recipient: 27 | return self._group_emails(recipient) 28 | return self._email_to_list(recipient) 29 | 30 | def _send(self): 31 | module = import_module(self._provider) 32 | message = clean_html(self._message) 33 | for phone in self._recipient_list: 34 | if self._from_email: 35 | module.send( 36 | phone, message, sms_from=self._from_email, **self._kwargs) 37 | else: 38 | module.send(phone, message, **self._kwargs) 39 | 40 | 41 | class SenderDebug(Sender): 42 | def _send(self): 43 | self.debug('Provider', self._provider or self.provider) 44 | self.debug('Message', clean_html(self._message)) 45 | self.debug('Recipients', self._recipient_list) 46 | self.debug('Sms from', self._from_email) 47 | self.debug('Additional params', self._kwargs) 48 | -------------------------------------------------------------------------------- /dbmail/backends/tts.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from dbmail.backends.sms import SenderDebug as SenderDebugBase 4 | from dbmail.backends.sms import Sender as SenderBase 5 | from dbmail.defaults import TTS_PROVIDER 6 | 7 | 8 | class Sender(SenderBase): 9 | provider = TTS_PROVIDER 10 | 11 | 12 | class SenderDebug(SenderDebugBase): 13 | provider = TTS_PROVIDER 14 | -------------------------------------------------------------------------------- /dbmail/defaults.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import os 4 | 5 | from django.utils.translation import ugettext_lazy as _ 6 | from django.conf import settings 7 | 8 | 9 | def get_settings(key, default): 10 | return getattr(settings, key, default) 11 | 12 | 13 | PRIORITY_STEPS = get_settings('DB_MAILER_PRIORITY_STEPS', ( 14 | (0, _("High")), 15 | (3, _("Medium")), 16 | (6, _("Low")), 17 | (9, _("Deferred")), 18 | )) 19 | CELERY_QUEUE = get_settings('DB_MAILER_CELERY_QUEUE', 'default') 20 | PUSH_QUEUE = get_settings('DB_MAILER_PUSH_QUEUE', CELERY_QUEUE) 21 | SMS_QUEUE = get_settings('DB_MAILER_SMS_QUEUE', CELERY_QUEUE) 22 | TTS_QUEUE = get_settings('DB_MAILER_TTS_QUEUE', CELERY_QUEUE) 23 | BOT_QUEUE = get_settings('DB_MAILER_BOT_QUEUE', CELERY_QUEUE) 24 | SIGNALS_QUEUE = get_settings('DB_MAILER_SIGNALS_QUEUE', CELERY_QUEUE) 25 | SIGNALS_MAIL_QUEUE = get_settings('DB_MAILER_SIGNALS_MAIL_QUEUE', CELERY_QUEUE) 26 | SUBSCRIPTION_QUEUE = get_settings('DB_MAILER_SUBSCRIPTION_QUEUE', CELERY_QUEUE) 27 | TRACKING_QUEUE = get_settings('DB_MAILER_TRACKING_QUEUE', CELERY_QUEUE) 28 | ENABLE_CELERY = get_settings('DB_MAILER_ENABLE_CELERY', True) 29 | SHOW_CONTEXT = get_settings('DB_MAILER_SHOW_CONTEXT', False) 30 | READ_ONLY_ENABLED = get_settings('DB_MAILER_READ_ONLY_ENABLED', True) 31 | UPLOAD_TO = get_settings('DB_MAILER_UPLOAD_TO', 'mail_files') 32 | DEFAULT_CATEGORY = get_settings('DB_MAILER_DEFAULT_CATEGORY', None) 33 | DEFAULT_FROM_EMAIL = get_settings('DB_MAILER_DEFAULT_FROM_EMAIL', None) 34 | DEFAULT_SMS_FROM = get_settings('DB_MAILER_DEFAULT_SMS_FROM', None) 35 | DEFAULT_PUSH_FROM = get_settings('DB_MAILER_DEFAULT_PUSH_FROM', None) 36 | DEFAULT_BOT_FROM = get_settings('DB_MAILER_DEFAULT_BOT_FROM', None) 37 | DEFAULT_PRIORITY = get_settings('DB_MAILER_DEFAULT_PRIORITY', 6) 38 | TEMPLATES_PER_PAGE = get_settings('DB_MAILER_TEMPLATES_PER_PAGE', 20) 39 | SEND_RETRY = get_settings('DB_MAILER_SEND_RETRY', 3) 40 | SEND_RETRY_DELAY = get_settings('DB_MAILER_SEND_RETRY_DELAY', 300) 41 | SEND_RETRY_DELAY_DIRECT = get_settings('DB_MAILER_SEND_RETRY_DELAY_DIRECT', 6) 42 | SEND_MAX_TIME = get_settings('DB_MAILER_SEND_MAX_TIME', 30) 43 | WSGI_AUTO_RELOAD = get_settings('DB_MAILER_WSGI_AUTO_RELOAD', False) 44 | UWSGI_AUTO_RELOAD = get_settings('DB_MAILER_UWSGI_AUTO_RELOAD', False) 45 | ENABLE_LOGGING = get_settings('DB_MAILER_ENABLE_LOGGING', True) 46 | ADD_HEADER = get_settings('DB_MAILER_ADD_HEADER', False) 47 | LOGS_EXPIRE_DAYS = get_settings('DB_MAILER_LOGS_EXPIRE_DAYS', 7) 48 | ALLOWED_MODELS_ON_ADMIN = get_settings('DB_MAILER_ALLOWED_MODELS_ON_ADMIN', [ 49 | 'MailFromEmailCredential', 50 | 'MailFromEmail', 51 | 'MailCategory', 52 | 'MailTemplate', 53 | 'MailLog', 54 | 'MailGroup', 55 | 'Signal', 56 | 'ApiKey', 57 | 'MailBcc', 58 | 'MailLogTrack', 59 | 'MailBaseTemplate', 60 | 'MailSubscription', 61 | 'MailLogException', 62 | ]) 63 | MAIL_SUBSCRIPTION_MODEL = get_settings( 64 | 'DB_MAILER_MAIL_SUBSCRIPTION_MODEL', 'dbmail.models.MailSubscription') 65 | AUTH_USER_MODEL = get_settings('AUTH_USER_MODEL', 'auth.User') 66 | USE_CELERY_FOR_ADMIN_TEST = get_settings( 67 | 'DB_MAILER_USE_CELERY_FOR_ADMIN_TEST', True) 68 | CACHE_TTL = get_settings('DB_MAILER_CACHE_TIMEOUT', None) 69 | ENABLE_USERS = get_settings('DB_MAILER_ENABLE_USERS', False) 70 | SIGNAL_DEFERRED_DISPATCHER = get_settings( 71 | 'DB_MAILER_SIGNAL_DEFERRED_DISPATCHER', 'celery') 72 | SIGNAL_DB_DEFERRED_PURGE = get_settings( 73 | 'DB_MAILER_SIGNAL_DB_DEFERRED_PURGE', True) 74 | 75 | TRACK_ENABLE = get_settings('DB_MAILER_TRACK_ENABLE', True) 76 | TRACK_PIXEL = get_settings( 77 | 'DB_MAILER_TRACK_PIXEL', 78 | [ 79 | 'image/gif', 80 | "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00" 81 | "\x00\xff\xff\xff\x00\x00\x00\x21\xf9\x04\x01\x00" 82 | "\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00" 83 | "\x00\x02\x02\x44\x01\x00\x3b" 84 | ] 85 | ) 86 | TRACK_HTML = get_settings( 87 | 'DB_MAILER_TRACK_HTML', 88 | '
' 89 | '' 90 | '
') 91 | 92 | BACKEND = get_settings('DB_MAILER_BACKEND', { 93 | 'mail': 'dbmail.backends.mail', 94 | 'tts': 'dbmail.backends.tts', 95 | 'sms': 'dbmail.backends.sms', 96 | 'push': 'dbmail.backends.push', 97 | 'bot': 'dbmail.backends.bot', 98 | }) 99 | _BACKEND = {v: k for k, v in BACKEND.items()} 100 | BACKENDS_MODEL_CHOICES = get_settings('DB_MAILER_BACKENDS_MODEL_CHOICES', ( 101 | (BACKEND.get('mail'), _('MailBox')), 102 | (BACKEND.get('push'), _('Push')), 103 | (BACKEND.get('sms'), _('SMS')), 104 | (BACKEND.get('tts'), _('TTS')), 105 | (BACKEND.get('bot'), _('BOT')), 106 | )) 107 | SORTED_BACKEND_CHOICES = sorted(list(BACKEND.items())) 108 | 109 | SMS_PROVIDER = get_settings( 110 | 'DB_MAILER_SMS_PROVIDER', 'dbmail.providers.nexmo.sms') 111 | TTS_PROVIDER = get_settings( 112 | 'DB_MAILER_TTS_PROVIDER', 'dbmail.providers.nexmo.tts') 113 | PUSH_PROVIDER = get_settings( 114 | 'DB_MAILER_PUSH_PROVIDER', 'dbmail.providers.prowl.push') 115 | BOT_PROVIDER = get_settings( 116 | 'DB_MAILER_BOT_PROVIDER', 'dbmail.providers.telegram.bot') 117 | MAIL_PROVIDER = get_settings('DB_MAILER_MAIL_PROVIDER', None) 118 | 119 | SAFARI_PUSH_PATH = get_settings( 120 | 'DB_MAILER_SAFARI_PUSH_PATH', settings.STATIC_ROOT or '.') 121 | 122 | MESSAGE_HTML2TEXT = get_settings( 123 | 'DB_MAILER_MESSAGE_HTML2TEXT', 'dbmail.utils') 124 | 125 | IGNORE_BROWSE_APP = get_settings( 126 | 'DB_MAILER_IGNORE_BROWSE_APP', [ 127 | 'dbmail', 'sessions', 'admin', 'djcelery', 128 | 'auth', 'reversion', 'contenttypes' 129 | ]) 130 | 131 | MODEL_HTMLFIELD = get_settings( 132 | 'DB_MAILER_MODEL_HTMLFIELD', 'dbmail.fields.HTMLField') 133 | MODEL_SUBSCRIPTION_DATA_FIELD = get_settings( 134 | 'DB_MAILER_MODEL_SUBSCRIPTION_DATA_FIELD', 'dbmail.fields.DataTextField') 135 | APNS_PROVIDER_DEFAULT_ACTION = get_settings( 136 | 'DB_MAILER_APNS_PROVIDER_DEFAULT_ACTION', 'Show') 137 | 138 | DEBUG = settings.DEBUG and get_settings('DB_MAILER_DEBUG', False) 139 | -------------------------------------------------------------------------------- /dbmail/exceptions.py: -------------------------------------------------------------------------------- 1 | class StopSendingException(Exception): 2 | """ 3 | pre_send exception 4 | """ 5 | -------------------------------------------------------------------------------- /dbmail/fields.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from django.db import models 4 | 5 | from dbmail import app_installed 6 | 7 | HTMLField = models.TextField 8 | DataTextField = models.TextField 9 | 10 | if app_installed('tinymce'): 11 | try: 12 | from tinymce.models import HTMLField 13 | 14 | except ImportError: 15 | pass 16 | 17 | if app_installed('ckeditor'): 18 | try: 19 | from ckeditor.fields import RichTextField as HTMLField 20 | 21 | except ImportError: 22 | pass 23 | -------------------------------------------------------------------------------- /dbmail/fixtures/mailgun_base_templates.md: -------------------------------------------------------------------------------- 1 | # Responsive transactional HTML email templates 2 | 3 | Transactional HTML emails often get neglected. **Styling HTML email is painful**. Tables, inline CSS, unsupported CSS, desktop clients, web clients, mobile clients, various devices, various providers. 4 | 5 | We’ve tried to remove some of the pain for you and open-sourced a collection of common templates for transactional email. 6 | 7 |

8 | 9 | * [Action email](http://mailgun.github.io/transactional-email-templates/action.html) 10 | * [Email alert](http://mailgun.github.io/transactional-email-templates/alert.html) 11 | * [Billing email](http://mailgun.github.io/transactional-email-templates/billing.html) 12 | 13 | Each template is **responsive** and each has been **tested** in all the **popular email clients**. 14 | 15 | ## How to use 16 | 17 | * Use these email templates for your transactional emails 18 | * Use them as is or think of them as boilerplates for more detailed emails 19 | * Ensure you [inline the CSS](#inline-the-css) before sending the email out 20 | 21 | ## What are transactional emails? 22 | 23 | Typically any email that is triggered by or sent automatically from your application. 24 | 25 | * Welcome emails 26 | * Actionable emails 27 | * Password resets 28 | * Receipts 29 | * Monthly invoices 30 | * Support requests 31 | * App error alerts 32 | * Reminders 33 | * etc. 34 | 35 | ## Tested and verified 36 | 37 | We’ve tested these email templates across all the major desktop, web and mobile clients, using Litmus. [See the test results.](https://litmus.com/pub/333e2b6/screenshots) 38 | 39 | 40 | 41 | ## Authors 42 | 43 | [Mailgun Team](https://github.com/mailgun/) 44 | [Mailgun Blog](http://blog.mailgun.com/transactional-html-email-templates/) 45 | -------------------------------------------------------------------------------- /dbmail/locale/ru/LC_MESSAGES/django.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/locale/ru/LC_MESSAGES/django.mo -------------------------------------------------------------------------------- /dbmail/management/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/management/commands/clean_dbmail_cache.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from dbmail.models import MailTemplate, ApiKey 3 | 4 | 5 | class Command(BaseCommand): 6 | def handle(self, *args, **options): 7 | MailTemplate.clean_cache() 8 | ApiKey.clean_cache() 9 | -------------------------------------------------------------------------------- /dbmail/management/commands/clean_dbmail_logs.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from dbmail.defaults import LOGS_EXPIRE_DAYS 3 | from dbmail.models import MailLog 4 | 5 | 6 | class Command(BaseCommand): 7 | def handle(self, *args, **options): 8 | MailLog.cleanup(days=LOGS_EXPIRE_DAYS) 9 | -------------------------------------------------------------------------------- /dbmail/management/commands/dbmail_test_send.py: -------------------------------------------------------------------------------- 1 | import re 2 | import optparse 3 | 4 | from django.core.management.base import BaseCommand 5 | 6 | from dbmail.models import MailTemplate 7 | from dbmail.defaults import BACKEND 8 | from dbmail import db_sender 9 | 10 | 11 | def send_test_msg(pk, email, user=None, **kwargs): 12 | template = MailTemplate.objects.get(pk=pk) 13 | slug = template.slug 14 | var_list = re.findall('\{\{\s?(\w+)\s?\}\}', template.message) 15 | context = {} 16 | for var in var_list: 17 | context[var] = '%s' % var.upper().replace('_', '-') 18 | return db_sender(slug, email, user, context, **kwargs) 19 | 20 | 21 | class Command(BaseCommand): 22 | def add_arguments(self, parser): 23 | parser.add_argument('--email', default='localhost', help='Recipients') 24 | parser.add_argument('--pk', default=1, help='DBMail template id') 25 | parser.add_argument('--without-celery', 26 | action='store_true', 27 | default=False, dest='celery', 28 | help='Send direct message') 29 | parser.add_argument('--provider', help='Provider') 30 | parser.add_argument('--backend', help='Backend') 31 | 32 | @staticmethod 33 | def get_kwargs(options): 34 | kwargs = { 35 | 'use_celery': not options['celery'], 36 | 'backend': BACKEND['mail']} 37 | if options['provider']: 38 | kwargs['provider'] = options['provider'] 39 | if options['backend']: 40 | kwargs['backend'] = BACKEND[options['backend']] 41 | return kwargs 42 | 43 | def handle(self, *args, **options): 44 | send_test_msg( 45 | options['pk'], options['email'], **self.get_kwargs(options) 46 | ) 47 | print("Done. Message was sent.") 48 | -------------------------------------------------------------------------------- /dbmail/management/commands/load_dbmail_base_templates.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.core.management import call_command 3 | 4 | 5 | class Command(BaseCommand): 6 | def handle(self, *args, **options): 7 | call_command('loaddata', 'mailgun_base_templates', app_label='dbmail') 8 | -------------------------------------------------------------------------------- /dbmail/management/commands/send_dbmail_deferred_signal.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from django.utils.timezone import now 3 | 4 | from dbmail.defaults import SIGNAL_DB_DEFERRED_PURGE 5 | from dbmail.models import SignalDeferredDispatch 6 | 7 | 8 | class Command(BaseCommand): 9 | def __init__(self): 10 | super(Command, self).__init__() 11 | self.signal = None 12 | 13 | def done(self): 14 | if SIGNAL_DB_DEFERRED_PURGE is True: 15 | self.signal.delete() 16 | else: 17 | self.signal.done = True 18 | self.signal.save() 19 | 20 | def start(self): 21 | self.signal.done = False 22 | self.signal.save() 23 | 24 | @staticmethod 25 | def signals(): 26 | return SignalDeferredDispatch.objects.filter( 27 | done__isnull=True, eta__lte=now()) 28 | 29 | def handle(self, *args, **options): 30 | for self.signal in self.signals(): 31 | self.start() 32 | self.signal.run_task() 33 | self.done() 34 | -------------------------------------------------------------------------------- /dbmail/management/commands/update_dbmail_cache.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | from dbmail.models import MailTemplate 3 | 4 | 5 | class Command(BaseCommand): 6 | def handle(self, *args, **options): 7 | for obj in MailTemplate.objects.all(): 8 | MailTemplate.clean_cache() 9 | MailTemplate.get_template(obj.slug) 10 | -------------------------------------------------------------------------------- /dbmail/migrations/0002_auto_20150321_1539.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('dbmail', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='mailfromemail', 17 | name='email', 18 | field=models.CharField(help_text='For sms/tts you must specify name or number', unique=True, max_length=75, verbose_name='Email'), 19 | preserve_default=True, 20 | ), 21 | migrations.AlterField( 22 | model_name='mailgroupemail', 23 | name='email', 24 | field=models.CharField(help_text=b'For sms/tts you must specify number', max_length=75, verbose_name='Email'), 25 | preserve_default=True, 26 | ), 27 | migrations.AlterField( 28 | model_name='maillogemail', 29 | name='email', 30 | field=models.CharField(max_length=75), 31 | preserve_default=True, 32 | ), 33 | migrations.AlterField( 34 | model_name='mailtemplate', 35 | name='context_note', 36 | field=models.TextField(help_text='This is simple note field for context variables with description.', null=True, verbose_name='Context note', blank=True), 37 | preserve_default=True, 38 | ), 39 | migrations.AlterField( 40 | model_name='mailtemplate', 41 | name='from_email', 42 | field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, default=None, to='dbmail.MailFromEmail', blank=True, help_text='If not specified, then used default.', null=True, verbose_name='Message from'), 43 | preserve_default=True, 44 | ), 45 | migrations.AlterField( 46 | model_name='mailtemplate', 47 | name='is_html', 48 | field=models.BooleanField(default=True, help_text='For sms/tts must be text not html', verbose_name='Is html'), 49 | preserve_default=True, 50 | ), 51 | ] 52 | -------------------------------------------------------------------------------- /dbmail/migrations/0003_maillog_backend.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dbmail', '0002_auto_20150321_1539'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='maillog', 16 | name='backend', 17 | field=models.CharField(default=b'mail', editable=False, choices=[(b'tts', b'dbmail.backends.tts'), (b'mail', b'dbmail.backends.mail'), (b'sms', b'dbmail.backends.sms')], max_length=25, verbose_name='Backend', db_index=True), 18 | preserve_default=True, 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /dbmail/migrations/0004_auto_20150321_2214.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dbmail', '0003_maillog_backend'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='maillog', 16 | name='provider', 17 | field=models.CharField(default=None, editable=False, max_length=250, blank=True, null=True, verbose_name='Backend', db_index=True), 18 | preserve_default=True, 19 | ), 20 | migrations.AlterField( 21 | model_name='maillog', 22 | name='backend', 23 | field=models.CharField(default=b'mail', editable=False, choices=[(b'tts', b'dbmail.backends.tts'), (b'mail', b'dbmail.backends.mail'), (b'sms', b'dbmail.backends.sms'), (b'push', b'dbmail.backends.push')], max_length=25, verbose_name='Backend', db_index=True), 24 | preserve_default=True, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /dbmail/migrations/0005_auto_20150506_2201.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ('dbmail', '0004_auto_20150321_2214'), 10 | ] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name='MailBaseTemplate', 15 | fields=[ 16 | ('id', models.AutoField( 17 | verbose_name='ID', serialize=False, 18 | auto_created=True, primary_key=True)), 19 | ('name', models.CharField( 20 | unique=True, max_length=100, verbose_name='Name')), 21 | ('message', models.TextField( 22 | help_text='Basic template for mail messages. ' 23 | '{{content}} tag for msg.', 24 | verbose_name='Body')), 25 | ('created', models.DateTimeField( 26 | auto_now_add=True, verbose_name='Created')), 27 | ('updated', models.DateTimeField( 28 | auto_now=True, verbose_name='Updated')), 29 | ], 30 | options={ 31 | 'verbose_name': 'Mail base template', 32 | 'verbose_name_plural': 'Mail base templates', 33 | }, 34 | ), 35 | migrations.AddField( 36 | model_name='mailtemplate', 37 | name='base', 38 | field=models.ForeignKey( 39 | verbose_name='Basic template', blank=True, 40 | to='dbmail.MailBaseTemplate', null=True, on_delete=models.CASCADE), 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /dbmail/migrations/0006_auto_20150708_0714.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | from django.conf import settings 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('dbmail', '0005_auto_20150506_2201'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='MailSubscription', 18 | fields=[ 19 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 20 | ('backend', models.CharField(max_length=50, choices=[(b'dbmail.backends.mail', 'MailBox'), (b'dbmail.backends.push', 'Push'), (b'dbmail.backends.sms', 'SMS'), (b'dbmail.backends.tts', 'TTS')])), 21 | ('start_hour', models.CharField(default=b'00:00', max_length=5)), 22 | ('end_hour', models.CharField(default=b'23:59', max_length=5)), 23 | ('is_enabled', models.BooleanField(default=True, db_index=True)), 24 | ('is_checked', models.BooleanField(default=False, db_index=True)), 25 | ('defer_at_allowed_hours', models.BooleanField(default=False)), 26 | ('address', models.CharField(max_length=60)), 27 | ('user', models.ForeignKey(verbose_name='User', blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)), 28 | ], 29 | options={ 30 | 'verbose_name': 'Mail Subscription', 31 | }, 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /dbmail/migrations/0007_auto_20150708_2016.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dbmail', '0006_auto_20150708_0714'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name='mailsubscription', 16 | options={'verbose_name': 'Mail Subscription', 'verbose_name_plural': 'Mail Subscriptions'}, 17 | ), 18 | migrations.AlterField( 19 | model_name='mailsubscription', 20 | name='address', 21 | field=models.CharField(help_text='Must be phone number/email/token', unique=True, max_length=60, verbose_name='Address'), 22 | ), 23 | migrations.AlterField( 24 | model_name='mailsubscription', 25 | name='backend', 26 | field=models.CharField(default=b'dbmail.backends.mail', max_length=50, verbose_name='Backend', choices=[(b'dbmail.backends.mail', 'MailBox'), (b'dbmail.backends.push', 'Push'), (b'dbmail.backends.sms', 'SMS'), (b'dbmail.backends.tts', 'TTS')]), 27 | ), 28 | migrations.AlterField( 29 | model_name='mailsubscription', 30 | name='defer_at_allowed_hours', 31 | field=models.BooleanField(default=False, verbose_name='Defer at allowed hours'), 32 | ), 33 | migrations.AlterField( 34 | model_name='mailsubscription', 35 | name='end_hour', 36 | field=models.CharField(default=b'23:59', max_length=5, verbose_name='End hour'), 37 | ), 38 | migrations.AlterField( 39 | model_name='mailsubscription', 40 | name='is_checked', 41 | field=models.BooleanField(default=False, db_index=True, verbose_name='Is checked'), 42 | ), 43 | migrations.AlterField( 44 | model_name='mailsubscription', 45 | name='is_enabled', 46 | field=models.BooleanField(default=True, db_index=True, verbose_name='Is enabled'), 47 | ), 48 | migrations.AlterField( 49 | model_name='mailsubscription', 50 | name='start_hour', 51 | field=models.CharField(default=b'00:00', max_length=5, verbose_name='Start hour'), 52 | ), 53 | ] 54 | -------------------------------------------------------------------------------- /dbmail/migrations/0008_auto_20151007_1918.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dbmail', '0007_auto_20150708_2016'), 11 | ] 12 | 13 | operations = [ 14 | 15 | migrations.AlterField( 16 | model_name='mailbcc', 17 | name='email', 18 | field=models.EmailField(unique=True, max_length=254, verbose_name='Email'), 19 | ), 20 | migrations.AlterField( 21 | model_name='mailfromemail', 22 | name='email', 23 | field=models.CharField(help_text='For sms/tts/push you must specify name or number', unique=True, max_length=75, verbose_name='Email'), 24 | ), 25 | migrations.AlterField( 26 | model_name='maillog', 27 | name='log_id', 28 | field=models.CharField(verbose_name='Log ID', max_length=60, editable=False, db_index=True), 29 | ), 30 | migrations.AlterField( 31 | model_name='maillog', 32 | name='provider', 33 | field=models.CharField(default=None, editable=False, max_length=250, blank=True, null=True, verbose_name='Provider', db_index=True), 34 | ), 35 | migrations.AlterField( 36 | model_name='maillogemail', 37 | name='email', 38 | field=models.CharField(max_length=75, verbose_name='Recipient'), 39 | ), 40 | migrations.AlterField( 41 | model_name='mailtemplate', 42 | name='bcc_email', 43 | field=models.ManyToManyField(help_text=b'Blind carbon copy', to='dbmail.MailBcc', verbose_name='Bcc', blank=True), 44 | ), 45 | migrations.AlterField( 46 | model_name='mailtemplate', 47 | name='is_html', 48 | field=models.BooleanField(default=True, help_text='For sms/tts/push must be text not html', verbose_name='Is html'), 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /dbmail/migrations/0009_auto_20160311_0918.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dbmail', '0008_auto_20151007_1918'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='mailsubscription', 16 | name='address', 17 | field=models.CharField(help_text='Must be phone number/email/token', max_length=60, verbose_name='Address', db_index=True), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /dbmail/migrations/0010_auto_20160728_1645.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dbmail', '0009_auto_20160311_0918'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='mailsubscription', 16 | name='data', 17 | field=models.TextField(null=True, blank=True), 18 | ), 19 | migrations.AddField( 20 | model_name='mailsubscription', 21 | name='title', 22 | field=models.CharField(max_length=100, null=True, blank=True), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /dbmail/migrations/0011_auto_20160803_1024.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dbmail', '0010_auto_20160728_1645'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='mailsubscription', 16 | name='address', 17 | field=models.CharField(help_text='Must be phone number/email/token', max_length=255, verbose_name='Address', db_index=True), 18 | ), 19 | migrations.AlterField( 20 | model_name='mailsubscription', 21 | name='title', 22 | field=models.CharField(max_length=255, null=True, blank=True), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /dbmail/migrations/0012_auto_20160915_1340.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dbmail', '0011_auto_20160803_1024'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='maillogemail', 16 | name='email', 17 | field=models.CharField(max_length=350, verbose_name='Recipient'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /dbmail/migrations/0013_auto_20160923_2201.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dbmail', '0012_auto_20160915_1340'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='maillogexception', 16 | name='ignore', 17 | field=models.BooleanField(default=False, verbose_name='Ignore'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /dbmail/migrations/0014_auto_20170801_1206.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2017-08-01 12:06 3 | from __future__ import unicode_literals 4 | 5 | from dbmail.defaults import SORTED_BACKEND_CHOICES, BACKENDS_MODEL_CHOICES, BACKEND 6 | from dbmail.models import SubscriptionDataField 7 | from django.db import migrations, models 8 | import django.db.models.deletion 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | ('dbmail', '0013_auto_20160923_2201'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='maillog', 20 | name='backend', 21 | field=models.CharField(choices=SORTED_BACKEND_CHOICES, db_index=True, default=b'mail', editable=False, max_length=25, verbose_name='Backend'), 22 | ), 23 | migrations.AlterField( 24 | model_name='mailsubscription', 25 | name='address', 26 | field=models.CharField(db_index=True, help_text='Must be phone number/email/token', max_length=350, verbose_name='Address'), 27 | ), 28 | migrations.AlterField( 29 | model_name='mailsubscription', 30 | name='backend', 31 | field=models.CharField(choices=BACKENDS_MODEL_CHOICES, default=BACKEND.get('mail'), max_length=50, verbose_name='Backend'), 32 | ), 33 | migrations.AlterField( 34 | model_name='mailsubscription', 35 | name='data', 36 | field=SubscriptionDataField(blank=True, default=dict, null=True), 37 | ), 38 | migrations.AlterField( 39 | model_name='mailsubscription', 40 | name='title', 41 | field=models.CharField(blank=True, max_length=350, null=True), 42 | ), 43 | migrations.AlterField( 44 | model_name='mailtemplate', 45 | name='category', 46 | field=models.ForeignKey(blank=True, default=2, null=True, on_delete=django.db.models.deletion.CASCADE, to='dbmail.MailCategory', verbose_name='Category'), 47 | ), 48 | migrations.AlterField( 49 | model_name='mailtemplate', 50 | name='from_email', 51 | field=models.ForeignKey(blank=True, default=2, help_text='If not specified, then used default.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='dbmail.MailFromEmail', verbose_name='Message from'), 52 | ), 53 | ] 54 | -------------------------------------------------------------------------------- /dbmail/migrations/0015_auto_20180926_1206.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-09-26 12:06 3 | 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('dbmail', '0014_auto_20170801_1206'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='mailgroupemail', 17 | name='email', 18 | field=models.CharField(help_text='For sms/tts you must specify number', max_length=75, verbose_name='Email'), 19 | ), 20 | migrations.AlterField( 21 | model_name='maillog', 22 | name='backend', 23 | field=models.CharField(choices=[('bot', 'dbmail.backends.bot'), ('mail', 'dbmail.backends.mail'), ('push', 'dbmail.backends.push'), ('sms', 'dbmail.backends.sms'), ('tts', 'dbmail.backends.tts')], db_index=True, default='mail', editable=False, max_length=25, verbose_name='Backend'), 24 | ), 25 | migrations.AlterField( 26 | model_name='maillogemail', 27 | name='mail_type', 28 | field=models.CharField(choices=[('cc', 'CC'), ('bcc', 'BCC'), ('to', 'TO')], max_length=3, verbose_name='Mail type'), 29 | ), 30 | migrations.AlterField( 31 | model_name='mailsubscription', 32 | name='data', 33 | field=models.TextField(blank=True, null=True), 34 | ), 35 | migrations.AlterField( 36 | model_name='mailsubscription', 37 | name='end_hour', 38 | field=models.CharField(default='23:59', max_length=5, verbose_name='End hour'), 39 | ), 40 | migrations.AlterField( 41 | model_name='mailsubscription', 42 | name='start_hour', 43 | field=models.CharField(default='00:00', max_length=5, verbose_name='Start hour'), 44 | ), 45 | migrations.AlterField( 46 | model_name='mailtemplate', 47 | name='bcc_email', 48 | field=models.ManyToManyField(blank=True, help_text='Blind carbon copy', to='dbmail.MailBcc', verbose_name='Bcc'), 49 | ), 50 | migrations.AlterField( 51 | model_name='mailtemplate', 52 | name='category', 53 | field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='dbmail.MailCategory', verbose_name='Category'), 54 | ), 55 | migrations.AlterField( 56 | model_name='mailtemplate', 57 | name='from_email', 58 | field=models.ForeignKey(blank=True, default=None, help_text='If not specified, then used default.', null=True, on_delete=django.db.models.deletion.SET_NULL, to='dbmail.MailFromEmail', verbose_name='Message from'), 59 | ), 60 | migrations.AlterField( 61 | model_name='signal', 62 | name='rules', 63 | field=models.TextField(blank=True, default='{{ instance.email }}', help_text='Template should return email to send message. Example:{% if instance.is_active %}{{ instance.email }}{% endif %}.You can return a multiple emails separated by commas.', null=True, verbose_name='Rules'), 64 | ), 65 | migrations.AlterField( 66 | model_name='signal', 67 | name='signal', 68 | field=models.CharField(choices=[('pre_save', 'pre_save'), ('post_save', 'post_save'), ('pre_delete', 'pre_delete'), ('post_delete', 'post_delete'), ('m2m_changed', 'm2m_changed')], default='post_save', max_length=15, verbose_name='Signal'), 69 | ), 70 | migrations.AlterField( 71 | model_name='signal', 72 | name='interval', 73 | field=models.PositiveIntegerField(help_text='Specify interval to send messages after sometime. That very helpful for mailing on enterprise products.Interval must be set in the seconds.', verbose_name='Send interval', default=0), 74 | ), 75 | migrations.AlterField( 76 | model_name='mailtemplate', 77 | name='interval', 78 | field=models.PositiveIntegerField(help_text='\n Specify interval to send messages after sometime.\n Interval must be set in the seconds.\n ', verbose_name='Send interval', default=0), 79 | ), 80 | ] 81 | 82 | -------------------------------------------------------------------------------- /dbmail/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/migrations/__init__.py -------------------------------------------------------------------------------- /dbmail/providers/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/providers/apple/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/providers/apple/__init__.py -------------------------------------------------------------------------------- /dbmail/providers/apple/apns.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from binascii import a2b_hex 4 | from json import dumps 5 | from socket import socket, AF_INET, SOCK_STREAM 6 | from struct import pack 7 | from time import time 8 | 9 | try: 10 | from ssl import wrap_socket 11 | except ImportError: 12 | from socket import ssl as wrap_socket 13 | 14 | from django.conf import settings 15 | 16 | from dbmail import defaults 17 | from dbmail.providers.apple.errors import APNsError 18 | from dbmail import PY3 19 | 20 | 21 | def send(token_hex, message, **kwargs): 22 | """ 23 | Site: https://apple.com 24 | API: https://developer.apple.com 25 | Desc: iOS notifications 26 | """ 27 | is_enhanced = kwargs.pop('is_enhanced', False) 28 | identifier = kwargs.pop('identifier', 0) 29 | expiry = kwargs.pop('expiry', 0) 30 | 31 | alert = { 32 | "title": kwargs.pop("event"), 33 | "body": message, 34 | "action": kwargs.pop( 35 | 'apns_action', defaults.APNS_PROVIDER_DEFAULT_ACTION) 36 | } 37 | 38 | data = { 39 | "aps": { 40 | 'alert': alert, 41 | 'content-available': kwargs.pop('content_available', 0) and 1 42 | } 43 | } 44 | data['aps'].update(kwargs) 45 | payload = dumps(data, separators=(',', ':')) 46 | 47 | token = a2b_hex(token_hex) 48 | if is_enhanced is True: 49 | fmt = '!BIIH32sH%ds' % len(payload) 50 | expiry = expiry and time() + expiry 51 | notification = pack( 52 | fmt, 1, identifier, expiry, 53 | 32, token, len(payload), payload) 54 | else: 55 | token_length_bin = pack('>H', len(token)) 56 | payload_length_bin = pack('>H', len(payload)) 57 | zero_byte = bytes('\0', 'utf-8') if PY3 is True else '\0' 58 | payload = bytes(payload, 'utf-8') if PY3 is True else payload 59 | notification = ( 60 | zero_byte + token_length_bin + token + 61 | payload_length_bin + payload) 62 | 63 | sock = socket(AF_INET, SOCK_STREAM) 64 | sock.settimeout(3) 65 | sock.connect((settings.APNS_GW_HOST, settings.APNS_GW_PORT)) 66 | ssl = wrap_socket( 67 | sock, settings.APNS_KEY_FILE, 68 | settings.APNS_CERT_FILE, 69 | do_handshake_on_connect=False) 70 | 71 | result = ssl.write(notification) 72 | 73 | sock.close() 74 | ssl.close() 75 | 76 | if not result: 77 | raise APNsError 78 | 79 | return True 80 | -------------------------------------------------------------------------------- /dbmail/providers/apple/apns2.py: -------------------------------------------------------------------------------- 1 | from json import dumps 2 | 3 | from django.conf import settings 4 | from hyper import HTTP20Connection 5 | from hyper.tls import init_context 6 | 7 | from dbmail import defaults 8 | from dbmail.providers.apple.errors import APNsError 9 | 10 | 11 | def send(token_hex, message, **kwargs): 12 | """ 13 | Site: https://apple.com 14 | API: https://developer.apple.com 15 | Desc: iOS notifications 16 | 17 | Installation and usage: 18 | pip install hyper 19 | """ 20 | 21 | priority = kwargs.pop('priority', 10) 22 | topic = kwargs.pop('topic', None) 23 | 24 | alert = { 25 | "title": kwargs.pop("event"), 26 | "body": message, 27 | "action": kwargs.pop( 28 | 'apns_action', defaults.APNS_PROVIDER_DEFAULT_ACTION) 29 | } 30 | 31 | data = { 32 | "aps": { 33 | 'alert': alert, 34 | 'content-available': kwargs.pop('content_available', 0) and 1 35 | } 36 | } 37 | data['aps'].update(kwargs) 38 | payload = dumps(data, separators=(',', ':')) 39 | 40 | headers = { 41 | 'apns-priority': priority 42 | } 43 | if topic is not None: 44 | headers['apns-topic'] = topic 45 | 46 | ssl_context = init_context() 47 | ssl_context.load_cert_chain(settings.APNS_CERT_FILE) 48 | connection = HTTP20Connection( 49 | settings.APNS_GW_HOST, settings.APNS_GW_PORT, ssl_context=ssl_context) 50 | 51 | stream_id = connection.request( 52 | 'POST', '/3/device/{}'.format(token_hex), payload, headers) 53 | response = connection.get_response(stream_id) 54 | if response.status != 200: 55 | raise APNsError(response.read()) 56 | return True 57 | -------------------------------------------------------------------------------- /dbmail/providers/apple/errors.py: -------------------------------------------------------------------------------- 1 | class APNsError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /dbmail/providers/boxcar/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/providers/boxcar/push.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | 4 | try: 5 | from httplib import HTTPSConnection 6 | from urllib import urlencode 7 | except ImportError: 8 | from http.client import HTTPSConnection 9 | from urllib.parse import urlencode 10 | 11 | from dbmail.providers.prowl.push import from_unicode 12 | from dbmail import get_version 13 | 14 | 15 | class BoxcarError(Exception): 16 | pass 17 | 18 | 19 | def send(token, title, **kwargs): 20 | """ 21 | Site: https://boxcar.io/ 22 | API: http://help.boxcar.io/knowledgebase/topics/48115-boxcar-api 23 | Desc: Best app for system administrators 24 | """ 25 | headers = { 26 | "Content-type": "application/x-www-form-urlencoded", 27 | "User-Agent": "DBMail/%s" % get_version(), 28 | } 29 | 30 | data = { 31 | "user_credentials": token, 32 | "notification[title]": from_unicode(title), 33 | "notification[sound]": "notifier-2" 34 | } 35 | 36 | for k, v in kwargs.items(): 37 | data['notification[%s]' % k] = from_unicode(v) 38 | 39 | http = HTTPSConnection(kwargs.pop("api_url", "new.boxcar.io")) 40 | http.request( 41 | "POST", "/api/notifications", 42 | headers=headers, 43 | body=urlencode(data)) 44 | response = http.getresponse() 45 | 46 | if response.status != 201: 47 | raise BoxcarError(response.reason) 48 | return True 49 | -------------------------------------------------------------------------------- /dbmail/providers/centrifugo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/providers/centrifugo/__init__.py -------------------------------------------------------------------------------- /dbmail/providers/centrifugo/push.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | try: 4 | from urllib2 import urlopen, Request 5 | from urllib import urlencode 6 | except ImportError: 7 | from urllib.request import urlopen, Request 8 | from urllib.parse import urlencode 9 | 10 | import hmac 11 | from hashlib import sha256 12 | from json import dumps 13 | 14 | 15 | from django.conf import settings 16 | 17 | 18 | class CentrifugoError(Exception): 19 | pass 20 | 21 | 22 | def send(channel, data, **kwargs): 23 | if type(data) not in (set, dict, list, tuple): 24 | kwargs['message'] = data 25 | 26 | data = dumps([{ 27 | "method": "publish", 28 | "params": { 29 | "channel": channel, 30 | "data": kwargs, 31 | }, 32 | }]) 33 | sign = hmac.new(settings.CENTRIFUGO_TOKEN, data, sha256).hexdigest() 34 | payload = urlencode({'data': data, 'sign': sign}) 35 | 36 | response = urlopen( 37 | Request(settings.CENTRIFUGO_API), 38 | payload, 39 | timeout=10 40 | ) 41 | if response.code != 200: 42 | raise CentrifugoError(response.read()) 43 | return True 44 | -------------------------------------------------------------------------------- /dbmail/providers/google/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/providers/google/__init__.py -------------------------------------------------------------------------------- /dbmail/providers/google/android.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | try: 4 | from httplib import HTTPSConnection 5 | from urlparse import urlparse 6 | except ImportError: 7 | from http.client import HTTPSConnection 8 | from urllib.parse import urlparse 9 | 10 | from json import dumps, loads 11 | from django.conf import settings 12 | 13 | 14 | class GCMError(Exception): 15 | pass 16 | 17 | 18 | def send(user, message, **kwargs): 19 | """ 20 | Site: https://developers.google.com 21 | API: https://developers.google.com/cloud-messaging/ 22 | Desc: Android notifications 23 | """ 24 | 25 | headers = { 26 | "Content-type": "application/json", 27 | "Authorization": "key=" + kwargs.pop("gcm_key", settings.GCM_KEY) 28 | } 29 | 30 | hook_url = 'https://android.googleapis.com/gcm/send' 31 | 32 | data = { 33 | "registration_ids": [user], 34 | "data": { 35 | "title": kwargs.pop("event"), 36 | 'message': message, 37 | } 38 | } 39 | data['data'].update(kwargs) 40 | 41 | up = urlparse(hook_url) 42 | http = HTTPSConnection(up.netloc) 43 | http.request( 44 | "POST", up.path, 45 | headers=headers, 46 | body=dumps(data)) 47 | response = http.getresponse() 48 | 49 | if response.status != 200: 50 | raise GCMError(response.reason) 51 | 52 | body = response.read() 53 | if loads(body).get("failure") > 0: 54 | raise GCMError(repr(body)) 55 | return True 56 | -------------------------------------------------------------------------------- /dbmail/providers/google/browser.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from json import dumps, loads 4 | 5 | from django.conf import settings 6 | from pywebpush import WebPusher 7 | 8 | 9 | class GCMError(Exception): 10 | pass 11 | 12 | 13 | def send(reg_id, message, **kwargs): 14 | """ 15 | Site: https://developers.google.com 16 | API: https://developers.google.com/web/updates/2016/03/web-push-encryption 17 | Desc: Web Push notifications for Chrome and FireFox 18 | 19 | Installation: 20 | pip install 'pywebpush>=0.4.0' 21 | """ 22 | 23 | subscription_info = kwargs.pop('subscription_info') 24 | 25 | payload = { 26 | "title": kwargs.pop("event"), 27 | "body": message, 28 | "url": kwargs.pop("push_url", None) 29 | } 30 | payload.update(kwargs) 31 | 32 | wp = WebPusher(subscription_info) 33 | response = wp.send( 34 | dumps(payload), gcm_key=settings.GCM_KEY, 35 | ttl=kwargs.pop("ttl", 60)) 36 | 37 | if not response.ok or ( 38 | response.text and loads(response.text).get("failure") > 0): 39 | raise GCMError(response.text) 40 | return True 41 | -------------------------------------------------------------------------------- /dbmail/providers/http/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/providers/http/__init__.py -------------------------------------------------------------------------------- /dbmail/providers/http/push.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | try: 4 | from httplib import HTTPConnection, HTTPSConnection 5 | from urlparse import urlparse 6 | except ImportError: 7 | from http.client import HTTPSConnection, HTTPConnection 8 | from urllib.parse import urlparse 9 | 10 | from json import dumps 11 | 12 | from django.conf import settings 13 | 14 | 15 | class HTTPError(Exception): 16 | pass 17 | 18 | 19 | def send(hook_url, message, **kwargs): 20 | headers = { 21 | "Content-type": "application/json" 22 | } 23 | 24 | http_key = kwargs.pop("http_key", None) 25 | if not http_key and hasattr(settings, 'HTTP_KEY'): 26 | http_key = settings.HTTP_KEY 27 | headers["Authorization"] = "key={}".format(http_key) 28 | 29 | kwargs["msg"] = message 30 | 31 | up = urlparse(hook_url) 32 | if up.scheme == 'https': 33 | http = HTTPSConnection(up.netloc) 34 | else: 35 | http = HTTPConnection(up.netloc) 36 | 37 | http.request( 38 | "POST", up.path, 39 | headers=headers, 40 | body=dumps(kwargs)) 41 | response = http.getresponse() 42 | 43 | if response.status != 200: 44 | raise HTTPError(response.reason) 45 | return True 46 | -------------------------------------------------------------------------------- /dbmail/providers/iqsms/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/providers/iqsms/sms.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | try: 4 | from httplib import HTTPConnection 5 | from urllib import urlencode 6 | except ImportError: 7 | from http.client import HTTPConnection 8 | from urllib.parse import urlencode 9 | 10 | from base64 import b64encode 11 | 12 | from django.conf import settings 13 | 14 | from dbmail.providers.prowl.push import from_unicode 15 | from dbmail import get_version 16 | 17 | 18 | class IQSMSError(Exception): 19 | pass 20 | 21 | 22 | def send(sms_to, sms_body, **kwargs): 23 | """ 24 | Site: http://iqsms.ru/ 25 | API: http://iqsms.ru/api/ 26 | """ 27 | headers = { 28 | "User-Agent": "DBMail/%s" % get_version(), 29 | 'Authorization': 'Basic %s' % b64encode( 30 | "%s:%s" % ( 31 | settings.IQSMS_API_LOGIN, settings.IQSMS_API_PASSWORD 32 | )).decode("ascii") 33 | } 34 | 35 | kwargs.update({ 36 | 'phone': sms_to, 37 | 'text': from_unicode(sms_body), 38 | 'sender': kwargs.pop('sms_from', settings.IQSMS_FROM) 39 | }) 40 | 41 | http = HTTPConnection(kwargs.pop("api_url", "gate.iqsms.ru")) 42 | http.request("GET", "/send/?" + urlencode(kwargs), headers=headers) 43 | response = http.getresponse() 44 | 45 | if response.status != 200: 46 | raise IQSMSError(response.reason) 47 | 48 | body = response.read().strip() 49 | if '=accepted' not in body: 50 | raise IQSMSError(body) 51 | 52 | return int(body.split('=')[0]) 53 | -------------------------------------------------------------------------------- /dbmail/providers/microsoft/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/providers/microsoft/__init__.py -------------------------------------------------------------------------------- /dbmail/providers/microsoft/base.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | from xml.etree import ElementTree 3 | 4 | from django.conf import settings 5 | from requests import post 6 | 7 | 8 | class WPError(Exception): 9 | pass 10 | 11 | 12 | class MPNSBase(object): 13 | NOTIFICATION_CLS = None 14 | TARGET = None 15 | 16 | def __init__(self): 17 | self.headers = { 18 | 'Content-Type': 'text/xml', 19 | 'Accept': 'application/*', 20 | 'X-NotificationClass': self.NOTIFICATION_CLS, 21 | 'X-WindowsPhone-Target': self.TARGET, 22 | } 23 | ElementTree.register_namespace('wp', 'WPNotification') 24 | 25 | @staticmethod 26 | def serialize_tree(tree): 27 | buf = BytesIO() 28 | tree.write(buf, encoding='utf-8') 29 | contents = ( 30 | '' + 31 | buf.getvalue()) 32 | buf.close() 33 | return contents 34 | 35 | @staticmethod 36 | def attr(element, payload_param, payload): 37 | if payload_param in payload: 38 | element.attrib['attribute'] = payload[payload_param] 39 | 40 | @staticmethod 41 | def sub(parent, element, payload_param, payload): 42 | if payload_param in payload: 43 | el = ElementTree.SubElement(parent, element) 44 | el.text = payload[payload_param] 45 | return el 46 | 47 | def payload(self, payload): 48 | raise NotImplementedError 49 | 50 | def send(self, uri, payload, msg_id=None, callback_uri=None): 51 | if msg_id is not None: 52 | self.headers['X-MessageID'] = msg_id 53 | 54 | if callback_uri is not None: 55 | self.headers['X-CallbackURI'] = callback_uri 56 | 57 | res = post( 58 | uri, data=self.payload(payload), headers=self.headers, 59 | cert=settings.WP_CERT_FILE) 60 | status = res.headers.get('x-notificationstatus') 61 | if res.status_code == 200 and status != 'QueueFull': 62 | return True 63 | raise WPError(res.reason) 64 | -------------------------------------------------------------------------------- /dbmail/providers/microsoft/raw.py: -------------------------------------------------------------------------------- 1 | from dbmail.providers.microsoft.base import MPNSBase 2 | 3 | 4 | class MPNSRaw(MPNSBase): 5 | NOTIFICATION_CLS = 3 6 | TARGET = 'raw' 7 | 8 | def payload(self, payload): 9 | return payload 10 | 11 | 12 | def send(uri, *_, **kwargs): 13 | return MPNSRaw().send(uri, kwargs) 14 | -------------------------------------------------------------------------------- /dbmail/providers/microsoft/tile.py: -------------------------------------------------------------------------------- 1 | from dbmail.providers.microsoft.base import MPNSBase, ElementTree 2 | 3 | 4 | class MPNSTile(MPNSBase): 5 | NOTIFICATION_CLS = 1 6 | TARGET = 'token' 7 | 8 | @staticmethod 9 | def clear(parent, element, payload_param, payload): 10 | if payload_param in payload: 11 | el = ElementTree.SubElement(parent, element) 12 | if payload[payload_param] is None: 13 | el.attrib['Action'] = 'Clear' 14 | else: 15 | el.text = payload[payload_param] 16 | return el 17 | 18 | def payload(self, payload): 19 | root = ElementTree.Element("{WPNotification}Notification") 20 | tile = ElementTree.SubElement(root, '{WPNotification}Tile') 21 | self.attr(tile, 'id', payload) 22 | self.attr(tile, 'template', payload) 23 | self.sub( 24 | tile, '{WPNotification}BackgroundImage', 25 | 'background_image', payload) 26 | self.clear(tile, '{WPNotification}Count', 'count', payload) 27 | self.clear(tile, '{WPNotification}Title', 'title', payload) 28 | self.clear( 29 | tile, '{WPNotification}BackBackgroundImage', 30 | 'back_background_image', payload) 31 | self.clear(tile, '{WPNotification}BackTitle', 'back_title', payload) 32 | self.clear( 33 | tile, '{WPNotification}BackContent', 'back_content', payload) 34 | return self.serialize_tree(ElementTree.ElementTree(root)) 35 | 36 | 37 | def send(uri, message, **kwargs): 38 | kwargs['title'] = kwargs.pop("event", 'App') 39 | kwargs['text1'] = message 40 | return MPNSTile().send(uri, kwargs) 41 | -------------------------------------------------------------------------------- /dbmail/providers/microsoft/toast.py: -------------------------------------------------------------------------------- 1 | from dbmail.providers.microsoft.base import MPNSBase, ElementTree 2 | 3 | 4 | class MPNSToast(MPNSBase): 5 | NOTIFICATION_CLS = 2 6 | TARGET = 'toast' 7 | 8 | def payload(self, payload): 9 | root = ElementTree.Element("{WPNotification}Notification") 10 | toast = ElementTree.SubElement(root, '{WPNotification}Toast') 11 | self.sub(toast, '{WPNotification}Text1', 'text1', payload) 12 | self.sub(toast, '{WPNotification}Text2', 'text2', payload) 13 | self.sub(toast, '{WPNotification}Sound', 'sound', payload) 14 | self.sub(toast, '{WPNotification}Param', 'param', payload) 15 | self.sub(toast, '{WPNotification}Path', 'path', payload) 16 | return self.serialize_tree(ElementTree.ElementTree(root)) 17 | 18 | 19 | def send(uri, message, **kwargs): 20 | kwargs['text1'] = kwargs.pop("event", 'App') 21 | kwargs['text2'] = message 22 | return MPNSToast().send(uri, kwargs) 23 | -------------------------------------------------------------------------------- /dbmail/providers/nexmo/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/providers/nexmo/sms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | try: 4 | from urllib import urlopen, urlencode 5 | except ImportError: 6 | from urllib.request import urlopen 7 | from urllib.parse import urlencode 8 | from json import loads 9 | 10 | from django.conf import settings 11 | 12 | from dbmail.providers.prowl.push import from_unicode 13 | 14 | 15 | class NexmoSmsError(Exception): 16 | pass 17 | 18 | 19 | def send(sms_to, sms_body, **kwargs): 20 | api_url = kwargs.pop('api_url', 'https://rest.nexmo.com/sms/json') 21 | params = { 22 | 'api_key': settings.NEXMO_USERNAME, 23 | 'api_secret': settings.NEXMO_PASSWORD, 24 | 'from': kwargs.pop('sms_from', settings.NEXMO_FROM), 25 | 'to': sms_to.replace('+', ''), 26 | 'type': 'unicode', 27 | 'lg': settings.NEXMO_LANG, 28 | 'text': from_unicode(sms_body) 29 | } 30 | if kwargs: 31 | params.update(**kwargs) 32 | 33 | url = urlopen('%s?%s' % (api_url, urlencode(params))) 34 | messages = loads(url.read()) 35 | 36 | response = messages.get('messages') 37 | if response and response[0].get('error-text'): 38 | raise NexmoSmsError(messages['messages'][0]['error-text']) 39 | elif 'status' in messages and messages.get('status') != '0': 40 | raise NexmoSmsError(messages.get('error-text')) 41 | return messages 42 | -------------------------------------------------------------------------------- /dbmail/providers/nexmo/tts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | def send(tts_to, tts_body, **kwargs): 5 | from .sms import send 6 | 7 | kwargs['api_url'] = 'https://api.nexmo.com/tts/json' 8 | return send(tts_to, tts_body, **kwargs) 9 | -------------------------------------------------------------------------------- /dbmail/providers/parse_com/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/providers/parse_com/push.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | try: 4 | from httplib import HTTPSConnection 5 | except ImportError: 6 | from http.client import HTTPSConnection 7 | 8 | from json import dumps, loads 9 | 10 | from django.conf import settings 11 | 12 | from dbmail import get_version 13 | 14 | 15 | class ParseComError(Exception): 16 | pass 17 | 18 | 19 | def send(device_id, description, **kwargs): 20 | """ 21 | Site: http://parse.com 22 | API: https://www.parse.com/docs/push_guide#scheduled/REST 23 | Desc: Best app for system administrators 24 | """ 25 | headers = { 26 | "X-Parse-Application-Id": settings.PARSE_APP_ID, 27 | "X-Parse-REST-API-Key": settings.PARSE_API_KEY, 28 | "User-Agent": "DBMail/%s" % get_version(), 29 | "Content-type": "application/json", 30 | } 31 | 32 | data = { 33 | "where": { 34 | "user_id": device_id, 35 | }, 36 | "data": { 37 | "alert": description, 38 | "title": kwargs.pop("event") 39 | } 40 | } 41 | 42 | _data = kwargs.pop('data', None) 43 | if _data is not None: 44 | data.update(_data) 45 | 46 | http = HTTPSConnection(kwargs.pop("api_url", "api.parse.com")) 47 | http.request( 48 | "POST", "/1/push", 49 | headers=headers, 50 | body=dumps(data)) 51 | response = http.getresponse() 52 | 53 | if response.status != 200: 54 | raise ParseComError(response.reason) 55 | 56 | body = loads(response.read()) 57 | if body['error']: 58 | raise ParseComError(body['error']) 59 | return True 60 | -------------------------------------------------------------------------------- /dbmail/providers/prowl/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/providers/prowl/push.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | try: 4 | from httplib import HTTPSConnection 5 | from urllib import urlencode 6 | except ImportError: 7 | from http.client import HTTPSConnection 8 | from urllib.parse import urlencode 9 | 10 | from django.conf import settings 11 | from dbmail import get_version 12 | 13 | 14 | class ProwlError(Exception): 15 | pass 16 | 17 | 18 | def from_unicode(text, text_length=None): 19 | try: 20 | text = text.encode('utf-8', 'ignore') 21 | except UnicodeDecodeError: 22 | pass 23 | 24 | if text_length is not None: 25 | text = text[0:text_length] 26 | 27 | return text 28 | 29 | 30 | def send(api_key, description, **kwargs): 31 | """ 32 | Site: http://prowlapp.com 33 | API: http://prowlapp.com/api.php 34 | Desc: Best app for system administrators 35 | """ 36 | headers = { 37 | "User-Agent": "DBMail/%s" % get_version(), 38 | "Content-type": "application/x-www-form-urlencoded" 39 | } 40 | 41 | application = from_unicode(kwargs.pop("app", settings.PROWL_APP), 256) 42 | event = from_unicode(kwargs.pop("event", 'Alert'), 1024) 43 | description = from_unicode(description, 10000) 44 | 45 | data = { 46 | "apikey": api_key, 47 | "application": application, 48 | "event": event, 49 | "description": description, 50 | "priority": kwargs.pop("priority", 1) 51 | } 52 | 53 | provider_key = kwargs.pop("providerkey", None) 54 | url = kwargs.pop('url', None) 55 | 56 | if provider_key is not None: 57 | data["providerkey"] = provider_key 58 | 59 | if url is not None: 60 | data["url"] = url[0:512] 61 | 62 | http = HTTPSConnection(kwargs.pop("api_url", "api.prowlapp.com")) 63 | http.request( 64 | "POST", "/publicapi/add", 65 | headers=headers, 66 | body=urlencode(data)) 67 | response = http.getresponse() 68 | 69 | if response.status != 200: 70 | raise ProwlError(response.reason) 71 | return True 72 | -------------------------------------------------------------------------------- /dbmail/providers/pubnub/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/providers/pubnub/push.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from django.conf import settings 4 | from Pubnub import Pubnub 5 | 6 | 7 | class PushOverError(Exception): 8 | pass 9 | 10 | 11 | def send(channel, message, **kwargs): 12 | """ 13 | Site: http://www.pubnub.com/ 14 | API: https://www.mashape.com/pubnub/pubnub-network 15 | Desc: real-time browser notifications 16 | 17 | Installation and usage: 18 | pip install -U pubnub 19 | Tests for browser notification http://127.0.0.1:8000/browser_notification/ 20 | """ 21 | 22 | pubnub = Pubnub( 23 | publish_key=settings.PUBNUB_PUB_KEY, 24 | subscribe_key=settings.PUBNUB_SUB_KEY, 25 | secret_key=settings.PUBNUB_SEC_KEY, 26 | ssl_on=kwargs.pop('ssl_on', False), **kwargs) 27 | return pubnub.publish(channel=channel, message={"text": message}) 28 | -------------------------------------------------------------------------------- /dbmail/providers/pushall/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/providers/pushall/__init__.py -------------------------------------------------------------------------------- /dbmail/providers/pushall/push.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from json import loads 4 | from urllib import urlencode 5 | from urllib2 import urlopen, Request 6 | 7 | from django.conf import settings 8 | 9 | 10 | class PushAllError(Exception): 11 | pass 12 | 13 | 14 | def send(ch, message, **kwargs): 15 | """ 16 | Site: https://pushall.ru 17 | API: https://pushall.ru/blog/api 18 | Desc: App for notification to devices/browsers and messaging apps 19 | """ 20 | params = { 21 | 'type': kwargs.pop('req_type', 'self'), 22 | 'key': settings.PUSHALL_API_KEYS[ch]['key'], 23 | 'id': settings.PUSHALL_API_KEYS[ch]['id'], 24 | 'title': kwargs.pop( 25 | "title", settings.PUSHALL_API_KEYS[ch].get('title') or ""), 26 | 'text': message, 27 | 'priority': kwargs.pop( 28 | "priority", settings.PUSHALL_API_KEYS[ch].get('priority') or "0"), 29 | } 30 | if kwargs: 31 | params.update(**kwargs) 32 | 33 | response = urlopen( 34 | Request('https://pushall.ru/api.php'), 35 | urlencode(params), 36 | timeout=10 37 | ) 38 | 39 | if response.code != 200: 40 | raise PushAllError(response.read()) 41 | 42 | json = loads(response.read()) 43 | if json.get('error'): 44 | raise PushAllError(json.get('error')) 45 | 46 | return True 47 | -------------------------------------------------------------------------------- /dbmail/providers/pushover/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/providers/pushover/push.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | try: 4 | from httplib import HTTPSConnection 5 | from urllib import urlencode 6 | except ImportError: 7 | from http.client import HTTPSConnection 8 | from urllib.parse import urlencode 9 | 10 | from json import loads 11 | 12 | from django.conf import settings 13 | 14 | from dbmail import get_version 15 | from dbmail.providers.prowl.push import from_unicode 16 | 17 | 18 | class PushOverError(Exception): 19 | pass 20 | 21 | 22 | def send(user, message, **kwargs): 23 | """ 24 | Site: https://pushover.net/ 25 | API: https://pushover.net/api 26 | Desc: real-time notifications 27 | """ 28 | headers = { 29 | "Content-type": "application/x-www-form-urlencoded", 30 | "User-Agent": "DBMail/%s" % get_version(), 31 | } 32 | 33 | title = from_unicode(kwargs.pop("title", settings.PUSHOVER_APP)) 34 | message = from_unicode(message) 35 | 36 | data = { 37 | "token": settings.PUSHOVER_TOKEN, 38 | "user": user, 39 | "message": message, 40 | "title": title, 41 | "priority": kwargs.pop("priority", 0) 42 | } 43 | 44 | _data = kwargs.pop('data', None) 45 | if _data is not None: 46 | data.update(_data) 47 | 48 | http = HTTPSConnection(kwargs.pop("api_url", "api.pushover.net")) 49 | http.request( 50 | "POST", "/1/messages.json", 51 | headers=headers, 52 | body=urlencode(data)) 53 | response = http.getresponse() 54 | 55 | if response.status != 200: 56 | raise PushOverError(response.reason) 57 | 58 | body = loads(response.read()) 59 | if body.get('status') != 1: 60 | raise PushOverError(repr(body)) 61 | return True 62 | -------------------------------------------------------------------------------- /dbmail/providers/sendinblue/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/providers/sendinblue/__init__.py -------------------------------------------------------------------------------- /dbmail/providers/sendinblue/mail.py: -------------------------------------------------------------------------------- 1 | """SendInBlue mail provider.""" 2 | 3 | import base64 4 | import email 5 | 6 | from mailin import Mailin 7 | 8 | 9 | class SendInBlueError(Exception): 10 | """Custom exception.""" 11 | 12 | pass 13 | 14 | 15 | def email_list_to_email_dict(email_list): 16 | """Convert a list of email to a dict of email.""" 17 | if email_list is None: 18 | return {} 19 | result = {} 20 | for value in email_list: 21 | realname, address = email.utils.parseaddr(value) 22 | result[address] = realname if realname and address else address 23 | return result 24 | 25 | 26 | def email_address_to_list(email_address): 27 | """Convert an email address to a list.""" 28 | realname, address = email.utils.parseaddr(email_address) 29 | return ( 30 | [address, realname] if realname and address else 31 | [email_address, email_address] 32 | ) 33 | 34 | 35 | def send(sender_instance): 36 | """Send a transactional email using SendInBlue API. 37 | 38 | Site: https://www.sendinblue.com 39 | API: https://apidocs.sendinblue.com/ 40 | """ 41 | m = Mailin( 42 | "https://api.sendinblue.com/v2.0", 43 | sender_instance._kwargs.get("api_key") 44 | ) 45 | data = { 46 | "to": email_list_to_email_dict(sender_instance._recipient_list), 47 | "cc": email_list_to_email_dict(sender_instance._cc), 48 | "bcc": email_list_to_email_dict(sender_instance._bcc), 49 | "from": email_address_to_list(sender_instance._from_email), 50 | "subject": sender_instance._subject, 51 | } 52 | if sender_instance._template.is_html: 53 | data.update({ 54 | "html": sender_instance._message, 55 | "headers": {"Content-Type": "text/html; charset=utf-8"} 56 | }) 57 | else: 58 | data.update({"text": sender_instance._message}) 59 | if "attachments" in sender_instance._kwargs: 60 | data["attachment"] = {} 61 | for attachment in sender_instance._kwargs["attachments"]: 62 | data["attachment"][attachment[0]] = base64.b64encode(attachment[1]) 63 | result = m.send_email(data) 64 | if result["code"] != "success": 65 | raise SendInBlueError(result["message"]) 66 | -------------------------------------------------------------------------------- /dbmail/providers/slack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/providers/slack/__init__.py -------------------------------------------------------------------------------- /dbmail/providers/slack/push.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | try: 4 | from httplib import HTTPSConnection 5 | from urlparse import urlparse 6 | from urllib import urlencode 7 | except ImportError: 8 | from http.client import HTTPSConnection 9 | from urllib.parse import urlparse, urlencode 10 | 11 | from json import dumps 12 | 13 | from django.conf import settings 14 | 15 | from dbmail import get_version 16 | from dbmail.providers.prowl.push import from_unicode 17 | 18 | 19 | class SlackError(Exception): 20 | pass 21 | 22 | 23 | def send(channel, message, **kwargs): 24 | """ 25 | Site: https://slack.com 26 | API: https://api.slack.com 27 | Desc: real-time messaging 28 | """ 29 | headers = { 30 | "Content-type": "application/x-www-form-urlencoded", 31 | "User-Agent": "DBMail/%s" % get_version(), 32 | } 33 | 34 | username = from_unicode(kwargs.pop("username", settings.SLACK_USERNAME)) 35 | hook_url = from_unicode(kwargs.pop("hook_url", settings.SLACK_HOOCK_URL)) 36 | channel = from_unicode(channel or settings.SLACK_CHANNEL) 37 | emoji = from_unicode(kwargs.pop("emoji", "")) 38 | message = from_unicode(message) 39 | 40 | data = { 41 | "channel": channel, 42 | "username": username, 43 | "text": message, 44 | "icon_emoji": emoji, 45 | } 46 | 47 | _data = kwargs.pop('data', None) 48 | if _data is not None: 49 | data.update(_data) 50 | 51 | up = urlparse(hook_url) 52 | http = HTTPSConnection(up.netloc) 53 | http.request( 54 | "POST", up.path, 55 | headers=headers, 56 | body=urlencode({"payload": dumps(data)})) 57 | response = http.getresponse() 58 | 59 | if response.status != 200: 60 | raise SlackError(response.reason) 61 | 62 | body = response.read() 63 | if body != "ok": 64 | raise SlackError(repr(body)) 65 | return True 66 | -------------------------------------------------------------------------------- /dbmail/providers/smsaero/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/providers/smsaero/sms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | try: 4 | from httplib import HTTPConnection 5 | from urllib import urlencode 6 | except ImportError: 7 | from http.client import HTTPConnection 8 | from urllib.parse import urlencode 9 | 10 | 11 | from django.conf import settings 12 | 13 | from dbmail.providers.prowl.push import from_unicode 14 | from dbmail import get_version 15 | 16 | import json 17 | 18 | 19 | class AeroSmsError(Exception): 20 | pass 21 | 22 | 23 | def send(sms_to, sms_body, **kwargs): 24 | """ 25 | Site: http://smsaero.ru/ 26 | API: http://smsaero.ru/api/ 27 | """ 28 | headers = { 29 | "User-Agent": "DBMail/%s" % get_version(), 30 | } 31 | 32 | kwargs.update({ 33 | 'user': settings.SMSAERO_LOGIN, 34 | 'password': settings.SMSAERO_MD5_PASSWORD, 35 | 'from': kwargs.pop('sms_from', settings.SMSAERO_FROM), 36 | 'to': sms_to.replace('+', ''), 37 | 'text': from_unicode(sms_body), 38 | 'answer': 'json', 39 | }) 40 | 41 | http = HTTPConnection(kwargs.pop("api_url", "gate.smsaero.ru")) 42 | http.request("GET", "/send/?" + urlencode(kwargs), headers=headers) 43 | response = http.getresponse() 44 | 45 | if response.status != 200: 46 | raise AeroSmsError(response.reason) 47 | 48 | read = response.read().decode(response.headers.get_content_charset()) 49 | data = json.loads(read) 50 | 51 | status = None 52 | if 'result' in data: 53 | status = data['result'] 54 | 55 | sms_id = None 56 | if 'id' in data: 57 | sms_id = data['id'] 58 | 59 | if sms_id and status == 'accepted': 60 | return True 61 | return False 62 | -------------------------------------------------------------------------------- /dbmail/providers/smsbliss/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/dbmail/providers/smsbliss/__init__.py -------------------------------------------------------------------------------- /dbmail/providers/smsbliss/sms.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import requests 4 | 5 | from django.conf import settings 6 | 7 | from dbmail.providers.prowl.push import from_unicode 8 | 9 | 10 | class SMSBlissError(Exception): 11 | pass 12 | 13 | 14 | def send(to, message, sms_from=None, **kwargs): 15 | sender = SMSBlissProvider(sms_from=sms_from) 16 | return sender.send_message(to, message, **kwargs) 17 | 18 | 19 | class SMSBlissProvider(object): 20 | login = settings.SMSBLISS_LOGIN 21 | password = settings.SMSBLISS_PASSWORD 22 | url = settings.SMSBLISS_API_URL 23 | 24 | def __init__(self, **kwargs): 25 | self._from_msg = kwargs.pop('sms_from', settings.SMSBLISS_FROM) 26 | 27 | def send_message(self, sms_to, message, **kwargs): 28 | kwargs.update({ 29 | 'messages': [{ 30 | 'clientId': 1, 31 | 'phone': sms_to, 32 | 'text': from_unicode(message), 33 | 'sender': self._from_msg 34 | }], 35 | 'showBillingDetails': True 36 | }) 37 | return self._send_request(kwargs) 38 | 39 | def _send_request(self, data_to_send): 40 | data = self.add_credentials(data_to_send) 41 | response = requests.post(self.url, data=json.dumps(data)) 42 | 43 | response_data = response.json() 44 | 45 | is_success = self.is_success_response(response, response_data) 46 | 47 | if not is_success: 48 | raise SMSBlissError(response_data) 49 | 50 | return response_data 51 | 52 | def add_credentials(self, data_to_send): 53 | data_to_send.update({ 54 | 'login': self.login, 55 | 'password': self.password, 56 | }) 57 | return data_to_send 58 | 59 | @staticmethod 60 | def is_success_response(response, response_data): 61 | if response.status_code != 200: 62 | return False 63 | 64 | response_messages = response_data.get('messages', []) 65 | 66 | for message in response_messages: 67 | if isinstance(message, dict): 68 | if message.get('status') not in ['accepted', 'delivered']: 69 | return False 70 | return True 71 | -------------------------------------------------------------------------------- /dbmail/providers/telegram/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8; 2 | -------------------------------------------------------------------------------- /dbmail/providers/telegram/bot.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import telepot 4 | from django.conf import settings 5 | 6 | 7 | def send(to, message, **kwargs): 8 | """ 9 | SITE: https://github.com/nickoala/telepot 10 | TELEGRAM API: https://core.telegram.org/bots/api 11 | 12 | Installation: 13 | pip install 'telepot>=10.4' 14 | """ 15 | 16 | available_kwargs_keys = [ 17 | 'parse_mode', 18 | 'disable_web_page_preview', 19 | 'disable_notification', 20 | 'reply_to_message_id', 21 | 'reply_markup' 22 | ] 23 | 24 | available_kwargs = { 25 | k: v for k, v in kwargs.iteritems() if k in available_kwargs_keys 26 | } 27 | 28 | bot = telepot.Bot(settings.TELEGRAM_BOT_TOKEN) 29 | return bot.sendMessage(to, message, **available_kwargs) 30 | -------------------------------------------------------------------------------- /dbmail/providers/twilio/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | __author__ = 'gotlium' 4 | -------------------------------------------------------------------------------- /dbmail/providers/twilio/sms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | try: 4 | from httplib import HTTPSConnection 5 | from urllib import urlencode 6 | except ImportError: 7 | from http.client import HTTPSConnection 8 | from urllib.parse import urlencode 9 | 10 | from base64 import b64encode 11 | from json import loads 12 | 13 | from django.conf import settings 14 | 15 | from dbmail.providers.prowl.push import from_unicode 16 | from dbmail import get_version 17 | 18 | 19 | class TwilioSmsError(Exception): 20 | pass 21 | 22 | 23 | def send(sms_to, sms_body, **kwargs): 24 | """ 25 | Site: https://www.twilio.com/ 26 | API: https://www.twilio.com/docs/api/rest/sending-messages 27 | """ 28 | headers = { 29 | "Content-type": "application/x-www-form-urlencoded", 30 | "User-Agent": "DBMail/%s" % get_version(), 31 | 'Authorization': 'Basic %s' % b64encode( 32 | "%s:%s" % ( 33 | settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN 34 | )).decode("ascii") 35 | 36 | } 37 | 38 | kwargs.update({ 39 | 'From': kwargs.pop('sms_from', settings.TWILIO_FROM), 40 | 'To': sms_to, 41 | 'Body': from_unicode(sms_body) 42 | }) 43 | 44 | http = HTTPSConnection(kwargs.pop("api_url", "api.twilio.com")) 45 | http.request( 46 | "POST", 47 | "/2010-04-01/Accounts/%s/Messages.json" % settings.TWILIO_ACCOUNT_SID, 48 | headers=headers, 49 | body=urlencode(kwargs)) 50 | 51 | response = http.getresponse() 52 | if response.status != 201: 53 | raise TwilioSmsError(response.reason) 54 | 55 | return loads(response.read()).get('sid') 56 | -------------------------------------------------------------------------------- /dbmail/signals.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import datetime 4 | 5 | from django.core.exceptions import ObjectDoesNotExist 6 | from django.template import Template, Context 7 | from django.contrib.sites.models import Site 8 | from django.contrib.auth.models import User 9 | from django.db.models import signals 10 | from django import dispatch 11 | 12 | from dbmail.defaults import ( 13 | SIGNALS_QUEUE, SIGNALS_MAIL_QUEUE, SIGNAL_DEFERRED_DISPATCHER, 14 | ENABLE_USERS, SEND_RETRY, SEND_RETRY_DELAY, ENABLE_CELERY 15 | ) 16 | from dbmail.models import Signal, SignalDeferredDispatch 17 | 18 | 19 | class SignalReceiver(object): 20 | def __init__(self, sender, **kwargs): 21 | self.sender = sender 22 | self.kwargs = kwargs 23 | self._kwargs = kwargs.copy() 24 | self.site = Site.objects.get_current() 25 | self.instance = kwargs.get('instance') 26 | self.pk = self.instance and self.instance.pk or None 27 | 28 | self.signal = None 29 | self.signal_pk = self.kwargs.pop('signal_pk', None) 30 | 31 | self.kwargs['old_instance'] = self.get_old_instance() 32 | self.kwargs['users'] = self.get_users() 33 | self.kwargs['date'] = datetime.date.today() 34 | self.kwargs['date_time'] = datetime.datetime.now() 35 | 36 | def get_signal_list(self): 37 | if not hasattr(self.sender._meta, 'module_name'): 38 | return Signal.objects.filter( 39 | model__model=self.sender._meta.model_name, 40 | is_active=True 41 | ) 42 | return Signal.objects.filter( 43 | model__model=self.sender._meta.module_name, 44 | is_active=True 45 | ) 46 | 47 | def get_email_list(self): 48 | if self.signal.group: 49 | return self.signal.group.slug 50 | 51 | email_list = Template(self.signal.rules).render(Context(self.kwargs)) 52 | self.kwargs.pop('users', None) 53 | return email_list.strip().replace('\r', '').replace('\n', '') 54 | 55 | def get_interval(self): 56 | options = dict() 57 | if self.signal.interval >= 0 and not self.signal_pk: 58 | options['send_after'] = self.signal.interval 59 | return options 60 | 61 | @staticmethod 62 | def get_users(): 63 | if ENABLE_USERS: 64 | return User.objects.filter( 65 | is_active=True, is_staff=False, is_superuser=False) 66 | return [] 67 | 68 | def get_old_instance(self): 69 | try: 70 | instance = self.kwargs.get('instance') 71 | if instance and instance.pk: 72 | return self.sender.objects.get( 73 | pk=self.kwargs['instance'].pk) 74 | except ObjectDoesNotExist: 75 | pass 76 | 77 | def get_current_instance(self): 78 | try: 79 | if self.instance and self.instance.pk and self.signal.update_model: 80 | obj = self.instance._default_manager.get(pk=self.instance.pk) 81 | self.kwargs['current_instance'] = obj 82 | except ObjectDoesNotExist: 83 | pass 84 | 85 | def send_mail(self): 86 | from dbmail import send_db_mail 87 | 88 | email_list = self.get_email_list() 89 | if email_list and not self.signal.is_sent(self.pk): 90 | for email in email_list.split(","): 91 | email = email.strip() 92 | if email: 93 | send_db_mail( 94 | self.signal.template.slug, email, self.site, 95 | self.kwargs, self.instance, queue=SIGNALS_MAIL_QUEUE, 96 | **self.get_interval() 97 | ) 98 | self.signal.mark_as_sent(self.pk) 99 | 100 | def _dispatch_deferred_task(self): 101 | self._kwargs['signal_pk'] = self.signal.pk 102 | 103 | if SIGNAL_DEFERRED_DISPATCHER == 'celery': 104 | from dbmail import tasks 105 | 106 | tasks.deferred_signal.apply_async( 107 | args=[self.sender], kwargs=self._kwargs, 108 | default_retry_delay=SEND_RETRY_DELAY, 109 | max_retries=SEND_RETRY, 110 | queue=SIGNALS_QUEUE, 111 | countdown=self.signal.interval 112 | ) 113 | else: 114 | SignalDeferredDispatch.add_task( 115 | args=[self.sender], kwargs=self._kwargs, 116 | params=dict( 117 | default_retry_delay=SEND_RETRY_DELAY, 118 | max_retries=SEND_RETRY, 119 | queue=SIGNALS_QUEUE 120 | ), interval=self.signal.interval 121 | ) 122 | 123 | def _run(self): 124 | if self.signal.interval: 125 | self._dispatch_deferred_task() 126 | else: 127 | self.send_mail() 128 | 129 | def run(self): 130 | for self.signal in self.get_signal_list(): 131 | self._run() 132 | 133 | def run_deferred(self): 134 | try: 135 | self.signal = Signal.objects.get(pk=self.signal_pk, is_active=True) 136 | self.get_current_instance() 137 | self.send_mail() 138 | except ObjectDoesNotExist: 139 | pass 140 | 141 | 142 | def signal_receiver(sender, **kwargs): 143 | from dbmail import celery_supported 144 | 145 | if 'signal' in kwargs: 146 | kwargs.pop('signal') 147 | 148 | if celery_supported() and ENABLE_CELERY: 149 | from dbmail import tasks 150 | 151 | tasks.signal_receiver.apply_async( 152 | args=[sender], kwargs=kwargs, 153 | default_retry_delay=SEND_RETRY_DELAY, 154 | max_retries=SEND_RETRY, 155 | queue=SIGNALS_QUEUE, 156 | ) 157 | else: 158 | SignalReceiver(sender, **kwargs).run() 159 | 160 | 161 | def initial_signals(): 162 | for signal in Signal.objects.filter(is_active=True): 163 | def_signal = getattr(signals, signal.signal) 164 | def_signal.connect( 165 | signal_receiver, sender=signal.model.model_class(), 166 | dispatch_uid=signal.model.name 167 | ) 168 | 169 | 170 | pre_send = dispatch.Signal() 171 | post_send = dispatch.Signal() 172 | post_exception = dispatch.Signal() 173 | 174 | safari_push_package = dispatch.Signal() 175 | safari_subscribe = dispatch.Signal() 176 | safari_unsubscribe = dispatch.Signal() 177 | safari_error_log = dispatch.Signal() 178 | 179 | push_subscribe = dispatch.Signal() 180 | push_unsubscribe = dispatch.Signal() 181 | -------------------------------------------------------------------------------- /dbmail/static/dbmail/admin/js/dbmail.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $(document).ready(function () { 3 | if (document.location.href.indexOf('/add/') == -1) { 4 | if ($('#searchbar').length == 0 && $('#grp-changelist-search').length == 0) { 5 | var test_button = '
  • Test template
  • '; 6 | var browse_button = '
  • Browse vars
  • '; 7 | if ($('.object-tools').length == 1) { 8 | $('.object-tools').append(test_button); 9 | $('.object-tools').append(browse_button); 10 | } 11 | else if ($('.grp-object-tools').length == 1) { 12 | $('.grp-object-tools').append(test_button); 13 | $('.grp-object-tools').append(browse_button); 14 | } 15 | } 16 | } 17 | 18 | test_app_template = function () { 19 | location.href = document.location.href.replace('change/', '') + 'sendmail/'; 20 | }; 21 | 22 | show_apps_dialog = function () { 23 | var appUrl = document.location.href.replace('change/', '') + 'sendmail/apps/'; 24 | $( 25 | '
    ' 26 | ).dialog({ 27 | modal: true, 28 | draggable: true, 29 | height: 500, 30 | width: 350, 31 | resizable: true 32 | } 33 | ).position({ 34 | my: "center", 35 | at: "center", 36 | of: window 37 | } 38 | ).show() 39 | ; 40 | } 41 | ; 42 | }); 43 | })((typeof(grp) == "undefined") ? django.jQuery : grp.jQuery); 44 | -------------------------------------------------------------------------------- /dbmail/tasks.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from celery import shared_task as task 4 | 5 | from dbmail.defaults import SEND_RETRY_DELAY, SEND_RETRY, SEND_MAX_TIME, DEBUG 6 | 7 | 8 | @task(name='dbmail.db_sender', default_retry_delay=SEND_RETRY_DELAY) 9 | def db_sender(*args, **kwargs): 10 | from dbmail import import_module 11 | 12 | retry_delay = kwargs.pop('retry_delay', SEND_RETRY_DELAY) 13 | time_limit = kwargs.pop('time_limit', SEND_MAX_TIME) 14 | max_retries = kwargs.pop('max_retries', SEND_RETRY) 15 | backend = import_module(kwargs.get('backend')) 16 | retry = kwargs.pop('retry', True) 17 | 18 | try: 19 | if DEBUG is True: 20 | return backend.SenderDebug(*args, **kwargs).send(is_celery=True) 21 | return backend.Sender(*args, **kwargs).send(is_celery=True) 22 | except Exception as exc: 23 | if retry is True and max_retries: 24 | raise db_sender.retry( 25 | retry=retry, max_retries=max_retries, 26 | countdown=retry_delay, exc=exc, 27 | time_limit=time_limit, 28 | ) 29 | raise 30 | 31 | 32 | @task(name='dbmail.subscription') 33 | def db_subscription(*args, **kwargs): 34 | from dbmail import import_by_string 35 | from dbmail.defaults import MAIL_SUBSCRIPTION_MODEL 36 | 37 | MailSubscription = import_by_string(MAIL_SUBSCRIPTION_MODEL) 38 | 39 | MailSubscription.notify(*args, **kwargs) 40 | 41 | 42 | @task(name='dbmail.signal_receiver') 43 | def signal_receiver(*args, **kwargs): 44 | from dbmail.signals import SignalReceiver 45 | 46 | SignalReceiver(*args, **kwargs).run() 47 | if len(args): 48 | return args[0]._meta.module_name 49 | 50 | 51 | @task(name='dbmail.deferred_signal') 52 | def deferred_signal(*args, **kwargs): 53 | from dbmail.signals import SignalReceiver 54 | 55 | SignalReceiver(*args, **kwargs).run_deferred() 56 | return 'OK' 57 | 58 | 59 | @task(name='dbmail.mail_track') 60 | def mail_track(http_meta, encrypted): 61 | from dbmail.models import MailLogTrack 62 | 63 | try: 64 | MailLogTrack.track(http_meta, encrypted) 65 | except Exception as exc: 66 | raise mail_track.retry( 67 | retry=True, max_retries=SEND_RETRY, 68 | countdown=SEND_RETRY_DELAY, exc=exc, 69 | ) 70 | -------------------------------------------------------------------------------- /dbmail/templates/dbmail/admin/change_list_link.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin/change_list.html' %} 2 | {% load i18n %} 3 | 4 | {% block object-tools-items %} 5 |
  • 6 | {% trans 'Clean cache' %} 7 |
  • 8 | 9 | {{ block.super }} 10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /dbmail/templates/dbmail/apps.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% for app, data in apps_list.items %} 9 | {{app|upper}}
    10 | 15 | {% endfor %} 16 | 17 | -------------------------------------------------------------------------------- /dbmail/templates/dbmail/browse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Back 9 |
    10 | {% for field,description in fields_list.items %} 11 | {{{{ field }}}} - {{description|capfirst}}
    12 | {% empty %} 13 | Fields for this application is not available 14 | {% endfor %} 15 | 16 | 17 | -------------------------------------------------------------------------------- /dbmail/translation.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from modeltranslation.translator import translator, TranslationOptions 4 | from dbmail.models import MailTemplate, MailBaseTemplate 5 | 6 | 7 | class MailTemplateTranslationOptions(TranslationOptions): 8 | fields = ('subject', 'message',) 9 | 10 | 11 | class MailBaseTemplateTranslationOptions(TranslationOptions): 12 | fields = ('message',) 13 | 14 | 15 | translator.register(MailTemplate, MailTemplateTranslationOptions) 16 | translator.register(MailBaseTemplate, MailBaseTemplateTranslationOptions) 17 | -------------------------------------------------------------------------------- /dbmail/urls.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from django.conf.urls import url 4 | 5 | from dbmail.views import ( 6 | send_by_dbmail, mail_read_tracker, 7 | SafariPushPackagesView, SafariSubscriptionView, SafariLogView, 8 | PushSubscriptionView 9 | ) 10 | 11 | urlpatterns = [ 12 | url(r'^api/', send_by_dbmail, name='db-mail-api'), 13 | url(r'^mail_read_tracker/(.*?)/$', 14 | mail_read_tracker, name='db-mail-tracker'), 15 | 16 | url(r'^safari/v(?P[0-9]{1})/pushPackages/(?P[.\w-]+)/?', 17 | SafariPushPackagesView.as_view()), 18 | url(r'^safari/v(?P[0-9]{1})/devices/' 19 | r'(?P[.\w-]+)/registrations/(?P[.\w-]+)/?', 20 | SafariSubscriptionView.as_view()), 21 | url(r'^safari/v(?P[0-9]{1})/log/?', SafariLogView.as_view()), 22 | 23 | url(r'^(?Pweb-push|mobile)/subscribe/', 24 | PushSubscriptionView.as_view(), name='push-subscribe'), 25 | url(r'^(?Pweb-push|mobile)/unsubscribe/', 26 | PushSubscriptionView.as_view(), name='push-unsubscribe'), 27 | ] 28 | -------------------------------------------------------------------------------- /dbmail/utils.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import logging 4 | 5 | from dbmail import import_module 6 | from django.utils.html import strip_tags 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def premailer_transform(text): 12 | try: 13 | from premailer import transform 14 | 15 | return transform(text) 16 | except ImportError: 17 | logger.error("You don't have module 'premailer' installed") 18 | return text 19 | except Exception as err: 20 | logger.error(err) 21 | return text 22 | 23 | 24 | def get_ip(request): 25 | try: 26 | from ipware.ip import get_real_ip 27 | 28 | ip = get_real_ip(request) 29 | if ip is not None: 30 | return ip.strip() 31 | except ImportError: 32 | pass 33 | 34 | return request.META['REMOTE_ADDR'].split(',')[-1].strip() 35 | 36 | 37 | def html2text(message): 38 | try: 39 | from html2text import html2text 40 | 41 | return html2text(message) 42 | except ImportError: 43 | return strip_tags(message) 44 | 45 | 46 | def clean_html(message): 47 | from dbmail.defaults import MESSAGE_HTML2TEXT 48 | 49 | module = import_module(MESSAGE_HTML2TEXT) 50 | return module.html2text(message) 51 | -------------------------------------------------------------------------------- /dbmail/views.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | import json 4 | import sys 5 | import os 6 | 7 | from django.http import HttpResponse, Http404, HttpResponseBadRequest 8 | from django.views.decorators.csrf import csrf_exempt 9 | from django.utils.decorators import method_decorator 10 | from django.shortcuts import get_object_or_404 11 | from django.views.generic import View 12 | from django.core.cache import cache 13 | 14 | from dbmail.models import ApiKey, MailLogTrack 15 | from dbmail import db_sender, celery_supported 16 | from dbmail import defaults 17 | 18 | from dbmail import signals 19 | 20 | 21 | allowed_fields = [ 22 | 'api_key', 'slug', 'recipient', 'from_email', 'cc', 'bcc', 23 | 'queue', 'retry_delay', 'max_retries', 'retry', 'language', 24 | 'time_limit', 'send_after', 'backend', 'provider', 25 | ] 26 | 27 | 28 | @csrf_exempt 29 | def send_by_dbmail(request): 30 | if request.method == 'POST': 31 | kwargs = dict() 32 | for f in allowed_fields: 33 | if request.POST.get(f): 34 | kwargs[f] = request.POST.get(f) 35 | 36 | backend = defaults.BACKEND.get(kwargs.pop('backend', 'mail')) 37 | api_key = kwargs.get('api_key') 38 | if api_key: 39 | del kwargs['api_key'] 40 | if not cache.get(api_key): 41 | get_object_or_404( 42 | ApiKey, api_key=api_key, is_active=True) 43 | cache.set(api_key, 1, timeout=defaults.CACHE_TTL) 44 | 45 | args = [] 46 | if request.POST.get('data'): 47 | args = [json.loads(request.POST['data'])] 48 | 49 | if kwargs.get('slug') and kwargs.get('recipient'): 50 | if backend is not None: 51 | kwargs['backend'] = backend 52 | 53 | db_sender( 54 | kwargs.pop('slug'), kwargs.pop('recipient'), 55 | *args, **kwargs) 56 | return HttpResponse('OK') 57 | raise Http404 58 | 59 | 60 | def mail_read_tracker(request, encrypted): 61 | if defaults.TRACK_ENABLE and defaults.ENABLE_LOGGING: 62 | req = {k: v for k, v in request.META.items() 63 | if k.startswith('HTTP_') or k.startswith('REMOTE')} 64 | if celery_supported() and defaults.ENABLE_CELERY: 65 | from dbmail.tasks import mail_track 66 | 67 | mail_track.apply_async( 68 | args=[req, encrypted], queue=defaults.TRACKING_QUEUE, 69 | retry=1, retry_policy={'max_retries': 3}) 70 | else: 71 | MailLogTrack.track(req, encrypted) 72 | 73 | return HttpResponse( 74 | content=defaults.TRACK_PIXEL[1], 75 | content_type=defaults.TRACK_PIXEL[0], 76 | ) 77 | 78 | 79 | class PostCSRFMixin(View): 80 | @method_decorator(csrf_exempt) 81 | def dispatch(self, request, *args, **kwargs): 82 | return super(PostCSRFMixin, self).dispatch(request, *args, **kwargs) 83 | 84 | 85 | class SafariPushPackagesView(PostCSRFMixin): 86 | def post(self, _, version, site_pid): 87 | signals.safari_push_package.send( 88 | self.__class__, instance=self, version=version, site_pid=site_pid) 89 | 90 | pp = os.path.join(defaults.SAFARI_PUSH_PATH, '%s.zip' % site_pid) 91 | return HttpResponse(open(pp).read(), content_type='application/zip') 92 | 93 | 94 | class SafariSubscriptionView(PostCSRFMixin): 95 | http_method_names = ['post', 'delete'] 96 | 97 | def post(self, _, **kwargs): 98 | signals.safari_subscribe.send(self.__class__, instance=self, **kwargs) 99 | return HttpResponse() 100 | 101 | def delete(self, _, **kwargs): 102 | signals.safari_unsubscribe.send( 103 | self.__class__, instance=self, **kwargs) 104 | return HttpResponse() 105 | 106 | 107 | class SafariLogView(PostCSRFMixin): 108 | def post(self, request, version): 109 | err = json.loads(request.body) 110 | signals.safari_error_log.send(self.__class__, instance=self, err=err) 111 | return HttpResponse() 112 | 113 | 114 | class PushSubscriptionView(View): 115 | http_method_names = ['post', 'delete'] 116 | 117 | def _process(self, request, signal, **kwargs): 118 | try: 119 | kwargs.update(json.loads(request.body)) 120 | signal.send(self.__class__, instance=self, **kwargs) 121 | return HttpResponse() 122 | except ValueError: 123 | return HttpResponseBadRequest() 124 | 125 | def post(self, request, **kwargs): 126 | return self._process(request, signals.push_subscribe, **kwargs) 127 | 128 | def delete(self, request, **kwargs): 129 | return self._process(request, signals.push_unsubscribe, **kwargs) 130 | -------------------------------------------------------------------------------- /demo/.vagrant_bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | apt-get update 4 | apt-get install -y redis-server git \ 5 | python3 python3-pip python3-dev libxml2-dev libxslt-dev zlib1g-dev 6 | 7 | # http://bugs.python.org/issue19846 8 | # > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK. 9 | export LANG=C.UTF-8 10 | 11 | pip3 install --upgrade pip 12 | pip3 install -r /mailer/requirements.txt 13 | 14 | if [ -f "demo/db.sqlite" ]; then rm ./demo/db.sqlite; fi 15 | 16 | python3 /mailer/manage.py migrate --noinput 17 | python3 /mailer/manage.py loaddata /mailer/auth.json 18 | 19 | nohup /bin/bash -c 'C_FORCE_ROOT=1 python3 /mailer/manage.py celeryd -Q default >& /dev/null & python3 /mailer/manage.py runserver 0.0.0.0:8000 >& /dev/null &' & 20 | -------------------------------------------------------------------------------- /demo/api.data.txt: -------------------------------------------------------------------------------- 1 | api_key=ZzriUzE&slug=welcome&recipient=root@local.host 2 | -------------------------------------------------------------------------------- /demo/auth.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pk": 1, 4 | "model": "auth.user", 5 | "fields": { 6 | "username": "admin", 7 | "first_name": "", 8 | "last_name": "", 9 | "is_active": true, 10 | "is_superuser": true, 11 | "is_staff": true, 12 | "last_login": "2015-03-24T10:51:29.626Z", 13 | "groups": [], 14 | "user_permissions": [], 15 | "password": "pbkdf2_sha256$10000$zbgKkZS39N0W$OXWLG0aehbEDvrbzVWG5WFersIBXO1jyU1WXeJhl8/o=", 16 | "email": "admin@local.host", 17 | "date_joined": "2015-03-24T10:43:04.483Z" 18 | } 19 | } 20 | ] -------------------------------------------------------------------------------- /demo/demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/demo/demo/__init__.py -------------------------------------------------------------------------------- /demo/demo/custom_backends/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/demo/demo/custom_backends/__init__.py -------------------------------------------------------------------------------- /demo/demo/custom_backends/double.py: -------------------------------------------------------------------------------- 1 | from dbmail.backends.sms import Sender as SmsSender 2 | from dbmail.backends.tts import Sender as TtsSender 3 | 4 | 5 | class Sender(object): 6 | def __init__(self, *args, **kwargs): 7 | self.args = args 8 | self.kwargs = kwargs 9 | 10 | def send(self, is_celery=True): 11 | SmsSender(*self.args, **self.kwargs).send(is_celery) 12 | TtsSender(*self.args, **self.kwargs).send(is_celery) 13 | return 'OK' 14 | 15 | 16 | class SenderDebug(Sender): 17 | def send(self, is_celery=True): 18 | print(self.args) 19 | print(self.kwargs) 20 | print({'is_celery': is_celery}) 21 | 22 | 23 | def send_double_notification(*args, **kwargs): 24 | from dbmail import db_sender 25 | 26 | kwargs['backend'] = 'demo.custom_backends.double' 27 | db_sender(*args, **kwargs) 28 | -------------------------------------------------------------------------------- /demo/demo/custom_backends/slack.py: -------------------------------------------------------------------------------- 1 | from dbmail.backends.mail import Sender as SenderBase 2 | from dbmail import import_module 3 | 4 | 5 | class Sender(SenderBase): 6 | # you're custom provider will be defined here 7 | provider = 'dbmail.providers.slack.push' 8 | 9 | def _get_recipient_list(self, recipient): 10 | if isinstance(recipient, list): 11 | return recipient 12 | return map(lambda x: x.strip(), recipient.split(',')) 13 | 14 | def _send(self): 15 | module = import_module(self.provider) 16 | for recipient in self._recipient_list: 17 | module.send(recipient, self._message) 18 | 19 | 20 | class SenderDebug(Sender): 21 | def _send(self): 22 | self.debug('Message', self._message) 23 | 24 | 25 | def send_db_slack(slug, *args, **kwargs): 26 | from dbmail import db_sender 27 | 28 | kwargs['backend'] = 'demo.custom_backends.slack' 29 | db_sender(slug, *args, **kwargs) 30 | -------------------------------------------------------------------------------- /demo/demo/dashboard.py: -------------------------------------------------------------------------------- 1 | from grappelli.dashboard import modules, Dashboard 2 | 3 | 4 | class DBMailerDashboard(Dashboard): 5 | def init_with_context(self, context): 6 | self.children.append(modules.ModelList( 7 | title='DBMailer', 8 | column=1, 9 | collapsible=True, 10 | models=( 11 | 'dbmail.models.*', 12 | ), 13 | )) 14 | -------------------------------------------------------------------------------- /demo/demo/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for demo project. 2 | 3 | import os 4 | import sys 5 | import django 6 | import dbmail 7 | 8 | PROJECT_ROOT = os.path.normpath(os.path.dirname(__file__)) 9 | 10 | DEBUG = True 11 | 12 | ADMINS = ( 13 | ('root', 'root@local.host'), 14 | ) 15 | 16 | MANAGERS = ADMINS 17 | 18 | DATABASES = { 19 | 'default': { 20 | 'ENGINE': 'django.db.backends.sqlite3', 21 | 'NAME': os.path.join(PROJECT_ROOT, 'db.sqlite'), 22 | } 23 | } 24 | 25 | ALLOWED_HOSTS = ['*'] 26 | 27 | TIME_ZONE = 'Europe/Moscow' 28 | LANGUAGE_CODE = 'en-us' 29 | LANGUAGES = ( 30 | ('ru', 'Russian'), 31 | ('en', 'English'), 32 | ) 33 | USE_I18N = True 34 | USE_L10N = True 35 | USE_TZ = True 36 | 37 | SITE_ID = 1 38 | 39 | MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media') 40 | STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static') 41 | 42 | MEDIA_URL = '/media/' 43 | STATIC_URL = '/static/' 44 | 45 | STATICFILES_FINDERS = ( 46 | 'django.contrib.staticfiles.finders.FileSystemFinder', 47 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 48 | 'django.contrib.staticfiles.finders.DefaultStorageFinder', 49 | ) 50 | 51 | SECRET_KEY = 'f969z_xc+^g*^gmt9oe7@og%kxd)54b!c!do)d7f2w2**f6%c0' 52 | 53 | TEMPLATES = [{ 54 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 55 | 'DIRS': [], 56 | 'APP_DIRS': True, 57 | 'OPTIONS': { 58 | 'context_processors': [ 59 | 'django.template.context_processors.debug', 60 | 'django.template.context_processors.request', 61 | 'django.contrib.auth.context_processors.auth', 62 | 'django.contrib.messages.context_processors.messages', 63 | 64 | ], 65 | }, 66 | }] 67 | 68 | MIDDLEWARE = ( 69 | 'django.middleware.security.SecurityMiddleware', 70 | 'django.contrib.sessions.middleware.SessionMiddleware', 71 | 'django.middleware.common.CommonMiddleware', 72 | 'django.middleware.csrf.CsrfViewMiddleware', 73 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 74 | 'django.contrib.messages.middleware.MessageMiddleware', 75 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 76 | ) 77 | 78 | ROOT_URLCONF = 'demo.urls' 79 | 80 | WSGI_APPLICATION = 'demo.wsgi.application' 81 | 82 | INSTALLED_APPS = [] 83 | 84 | if 'test' not in sys.argv: 85 | INSTALLED_APPS += [ 86 | # 'suit', 87 | 'grappelli.dashboard', 88 | 'grappelli', 89 | 'sslserver', 90 | ] 91 | 92 | INSTALLED_APPS += [ 93 | 'django.contrib.admin', 94 | 'django.contrib.auth', 95 | 'django.contrib.contenttypes', 96 | 'django.contrib.sessions', 97 | 'django.contrib.sites', 98 | 'django.contrib.messages', 99 | 'django.contrib.staticfiles', 100 | 101 | 'dbmail', 102 | ] 103 | 104 | if 'test' not in sys.argv: 105 | INSTALLED_APPS += [ 106 | 'django_extensions', 107 | 'reversion', 108 | 'reversion_compare', 109 | 'djcelery', 110 | 'ckeditor', 111 | # 'rosetta', 112 | ] 113 | 114 | if 'grappelli' not in INSTALLED_APPS: 115 | INSTALLED_APPS += ['admin_jqueryui'] 116 | 117 | 118 | LOGGING = { 119 | 'version': 1, 120 | 'disable_existing_loggers': True, 121 | 'root': { 122 | 'level': 'WARNING', 123 | 'handlers': ['console'], 124 | }, 125 | 'filters': { 126 | 'require_debug_false': { 127 | '()': 'django.utils.log.RequireDebugFalse' 128 | } 129 | }, 130 | 'handlers': { 131 | 'mail_admins': { 132 | 'level': 'ERROR', 133 | 'filters': ['require_debug_false'], 134 | 'class': 'django.utils.log.AdminEmailHandler' 135 | }, 136 | 'console': { 137 | 'level': 'DEBUG', 138 | 'class': 'logging.StreamHandler', 139 | # 'formatter': 'verbose' 140 | } 141 | }, 142 | 'loggers': { 143 | 'django.request': { 144 | 'handlers': ['console'], 145 | 'level': 'ERROR', 146 | 'propagate': True, 147 | } 148 | } 149 | } 150 | 151 | ROSETTA_STORAGE_CLASS = 'rosetta.storage.SessionRosettaStorage' 152 | ROSETTA_ENABLE_TRANSLATION_SUGGESTIONS = True 153 | 154 | 155 | ################ Django-Db-Mailer configuration ################ 156 | try: 157 | import djcelery 158 | 159 | djcelery.setup_loader() 160 | 161 | BROKER_URL = 'redis://127.0.0.1:6379/5' 162 | 163 | BROKER_TRANSPORT_OPTIONS = { 164 | 'priority_steps': range(10), 165 | } 166 | 167 | CELERY_QUEUES = { 168 | 'default': { 169 | "exchange": "default", 170 | "binding_key": "default", 171 | }, 172 | } 173 | 174 | CELERY_IGNORE_RESULT = True 175 | CELERY_ACCEPT_CONTENT = ['pickle'] 176 | REDIS_CONNECT_RETRY = True 177 | except ImportError: 178 | pass 179 | 180 | TINYMCE_DEFAULT_CONFIG = { 181 | 'plugins': "table,spellchecker,paste,searchreplace", 182 | 'theme': "advanced", 183 | 'cleanup_on_startup': True, 184 | 'custom_undo_redo_levels': 10, 185 | } 186 | 187 | CKEDITOR_UPLOAD_PATH = os.path.join(MEDIA_ROOT, 'uploads') 188 | 189 | CKEDITOR_CONFIGS = { 190 | 'default': { 191 | 'toolbar': None, 192 | 'height': 300, 193 | 'width': '100%', 194 | 'allowedContent': True, 195 | }, 196 | } 197 | 198 | SUIT_CONFIG = { 199 | 'ADMIN_NAME': 'Django DB Mailer v.%s' % dbmail.get_version(), 200 | 'SEARCH_URL': '', 201 | 'MENU': ( 202 | {'app': 'dbmail', 'label': 'DBMailer', 'icon': 'icon-align-justify'}, 203 | ) 204 | } 205 | 206 | GRAPPELLI_ADMIN_TITLE = 'Django DB Mailer v.%s' % dbmail.get_version() 207 | GRAPPELLI_INDEX_DASHBOARD = 'demo.dashboard.DBMailerDashboard' 208 | 209 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 210 | DEFAULT_FROM_EMAIL = 'Django ' 211 | 212 | 213 | if 'test' not in sys.argv: 214 | CACHES = { 215 | "default": { 216 | "BACKEND": "django_redis.cache.RedisCache", 217 | "LOCATION": "127.0.0.1:6379", 218 | } 219 | } 220 | else: 221 | CACHES = { 222 | 'default': { 223 | 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 224 | 'LOCATION': '/tmp/django_cache', 225 | } 226 | } 227 | 228 | # DbMail settings 229 | AUTH_USER_MODEL = 'auth.User' 230 | DB_MAILER_SHOW_CONTEXT = True 231 | DB_MAILER_WSGI_AUTO_RELOAD = False 232 | DB_MAILER_UWSGI_AUTO_RELOAD = True 233 | 234 | ''' 235 | # Translation settings 236 | MODELTRANSLATION_DEFAULT_LANGUAGE = 'en' 237 | MODELTRANSLATION_LANGUAGES = ('ru', 'en') 238 | MODELTRANSLATION_TRANSLATION_FILES = ( 239 | 'dbmail.translation', 240 | ) 241 | INSTALLED_APPS = ['modeltranslation'] + INSTALLED_APPS 242 | ''' 243 | 244 | # For detect info about user 245 | GEOIP_PATH = '/usr/share/GeoIP/' 246 | 247 | ############################################################ 248 | 249 | if django.VERSION < (1, 6): 250 | TEST_RUNNER = 'discover_runner.DiscoverRunner' 251 | 252 | try: 253 | from local_settings import * 254 | 255 | except ImportError: 256 | pass 257 | -------------------------------------------------------------------------------- /demo/demo/static/images/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/demo/demo/static/images/icon-256x256.png -------------------------------------------------------------------------------- /demo/demo/static/js/service-worker.js: -------------------------------------------------------------------------------- 1 | self.addEventListener('push', function (event) { 2 | if (event.data) { 3 | var payload = event.data.json(); 4 | 5 | return self.registration.showNotification(payload.title, { 6 | body: payload.body, 7 | icon: '/static/images/icon-256x256.png', 8 | data: payload 9 | }); 10 | } 11 | }); 12 | 13 | self.addEventListener('notificationclick', function (event) { 14 | event.notification.close(); 15 | 16 | if (event.notification.data && event.notification.data.url) { 17 | event.waitUntil(clients.matchAll({ 18 | type: "window" 19 | }).then(function () { 20 | if (clients.openWindow) { 21 | return clients.openWindow(event.notification.data.url); 22 | } 23 | })); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /demo/demo/static/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "gcm_sender_id": "611283449041", 3 | "name": "Push Demo", 4 | "short_name": "Push Demo", 5 | "icons": [ 6 | { 7 | "src": "/static/images/icon-256x256.png", 8 | "sizes": "256x256" 9 | } 10 | ], 11 | "start_url": "/?homescreen=1", 12 | "display": "standalone", 13 | "gcm_user_visible_only": true, 14 | "permissions": [ 15 | "gcm" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /demo/demo/templates/404.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/demo/demo/templates/404.html -------------------------------------------------------------------------------- /demo/demo/templates/500.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/demo/demo/templates/500.html -------------------------------------------------------------------------------- /demo/demo/templates/browser_notification.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Desktop Notifications 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 |
    17 | 18 |
    19 | 20 | 21 | 22 | 23 |
    24 | 25 | 26 | 27 | 28 | 29 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /demo/demo/templates/web-push.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web Push Notifications 6 | 7 | 64 | 65 | 66 |
    67 | {% csrf_token %} 68 | 69 | 70 | -------------------------------------------------------------------------------- /demo/demo/urls.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from django.conf.urls import include, url 4 | from django.contrib import admin 5 | from django.conf import settings 6 | 7 | import demo.views 8 | 9 | 10 | urlpatterns = [ 11 | url(r'^admin/', admin.site.urls), 12 | # url(r'^rosetta/', include('rosetta.urls')), 13 | url(r'^dbmail/', include('dbmail.urls')), 14 | ] 15 | 16 | if 'test' not in sys.argv: 17 | urlpatterns += [ 18 | url(r'^grappelli/', include('grappelli.urls')), 19 | url('^browser_notification/$', demo.views.browser_notification), 20 | url('^web-push/$', demo.views.web_push_notification), 21 | url(r'^ckeditor/', include('ckeditor_uploader.urls')), 22 | ] 23 | 24 | 25 | # For security reason 26 | if settings.DEBUG: 27 | from django.conf.urls.static import static 28 | 29 | urlpatterns += static(settings.MEDIA_URL, 30 | document_root=settings.MEDIA_ROOT) 31 | urlpatterns += static(settings.STATIC_URL, 32 | document_root=settings.STATIC_ROOT) 33 | 34 | -------------------------------------------------------------------------------- /demo/demo/views.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | from pprint import pprint 4 | 5 | from django.shortcuts import render 6 | 7 | from dbmail import signals 8 | 9 | 10 | def browser_notification(request): 11 | return render(request, "browser_notification.html") 12 | 13 | 14 | def web_push_notification(request): 15 | return render(request, "web-push.html") 16 | 17 | 18 | def _dump_push_signals(**kwargs): 19 | kwargs.pop('instance', None) 20 | kwargs.pop('sender', None) 21 | kwargs.pop('signal', None) 22 | pprint(kwargs) 23 | 24 | 25 | signals.safari_subscribe.connect(_dump_push_signals) 26 | signals.safari_unsubscribe.connect(_dump_push_signals) 27 | signals.safari_error_log.connect(_dump_push_signals) 28 | 29 | signals.push_subscribe.connect(_dump_push_signals) 30 | signals.push_unsubscribe.connect(_dump_push_signals) 31 | -------------------------------------------------------------------------------- /demo/demo/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for demo project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 19 | 20 | # This application object is used by any WSGI server configured to use this 21 | # file. This includes Django's development server, if the WSGI_APPLICATION 22 | # setting points here. 23 | from django.core.wsgi import get_wsgi_application 24 | application = get_wsgi_application() 25 | 26 | # Apply WSGI middleware here. 27 | # from helloworld.wsgi import HelloWorldApplication 28 | # application = HelloWorldApplication(application) 29 | -------------------------------------------------------------------------------- /demo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /demo/requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=2.0,<2.1 2 | django-bootstrap-static==2.3.2 3 | django-debug-toolbar==1.7 4 | django-extensions==1.7.8 5 | django-rosetta==0.7.13 6 | django-redis==4.7.0 7 | django-redis-cache==1.7.1 8 | django-celery==3.2.1 9 | celery>=3.1,<4 10 | 11 | # MySQL-python>=1.2.5 12 | Werkzeug>=0.12.1 13 | ipython>=5.3.0 14 | tornado>=4.5 15 | coverage>=4.3.4 16 | pyzmq>=16.0.2 17 | Jinja2>=2.9.6 18 | flake8>=3.3.0 19 | Sphinx>=1.5.5 20 | redis>=2.10.5 21 | wheel>=0.29.0 22 | 23 | django-grappelli==2.11.1 24 | django-suit==0.2.25 25 | django-ckeditor==5.2.2 26 | django-reversion==2.0.8 27 | django-reversion-compare==0.8.4 28 | diff-match-patch==20121119 29 | pytz>=2017.2 30 | django-sslserver==0.19 31 | geoip2==2.9.0 32 | 33 | -e git+https://github.com/hint/django-admin-jqueryui.git#egg=django-admin-jqueryui 34 | -e git+https://github.com/LPgenerator/django-db-mailer.git#egg=django-db-mailer 35 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = .build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-geoip-redis.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-geoip-redis.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-geoip-redis" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-geoip-redis" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." -------------------------------------------------------------------------------- /docs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/docs/__init__.py -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | API 4 | === 5 | 6 | Objective-C example 7 | ------------------- 8 | 9 | .. code-block:: objective-c 10 | 11 | NSURL *url = [NSURL URLWithString:@"http://127.0.0.1:8000/dbmail/api/"]; 12 | NSString *postString = @"api_key=ZzriUzE&slug=welcome&recipient=root@local.host"; 13 | NSData *returnData = [[NSData alloc]init]; 14 | 15 | NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; 16 | [request setHTTPMethod:@"POST"]; 17 | [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)[postString length]] forHTTPHeaderField:@"Content-length"]; 18 | [request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]]; 19 | returnData = [NSURLConnection sendSynchronousRequest: request returningResponse: nil error: nil]; 20 | 21 | NSString *response = [[NSString alloc] initWithBytes:[returnData bytes] length:[returnData length] encoding:NSUTF8StringEncoding]; 22 | NSLog(@"Response >>>> %@",response); 23 | 24 | 25 | Java example 26 | ------------ 27 | 28 | .. code-block:: java 29 | 30 | httpClient client = new DefaultHttpClient(); 31 | HttpPost post = new HttpPost("http://127.0.0.1:8000/dbmail/api/"); 32 | 33 | List pairs = new ArrayList(); 34 | pairs.add(new BasicNameValuePair("api_key", "ZzriUzE")); 35 | pairs.add(new BasicNameValuePair("slug", "welcome")); 36 | pairs.add(new BasicNameValuePair("recipient", "root@local.host")); 37 | post.setEntity(new UrlEncodedFormEntity(pairs)); 38 | 39 | client.execute(post); 40 | 41 | 42 | Python example 43 | -------------- 44 | 45 | .. code-block:: python 46 | 47 | from httplib import HTTPConnection 48 | from urlparse import urlparse 49 | from urllib import urlencode 50 | 51 | headers = { 52 | "Content-type": "application/x-www-form-urlencoded", 53 | "User-Agent": "DBMail Cli", 54 | } 55 | 56 | data = { 57 | "api_key": "ZzriUzE", 58 | "slug": "welcome", 59 | "recipient": "root@local.host" 60 | } 61 | 62 | uri = urlparse("http://127.0.0.1:8000/dbmail/api/") 63 | 64 | http = HTTPConnection(uri.netloc) 65 | http.request( 66 | "POST", uri.path, 67 | headers=headers, 68 | body=urlencode(data) 69 | ) 70 | print http.getresponse().read() 71 | 72 | 73 | 74 | Go example 75 | ---------- 76 | 77 | .. code-block:: go 78 | 79 | package main 80 | 81 | import ( 82 | "net/http" 83 | "net/url" 84 | "bytes" 85 | "fmt" 86 | ) 87 | 88 | func main() { 89 | uri := "http://127.0.0.1:8000/dbmail/api/" 90 | 91 | data := url.Values{} 92 | data.Add("api_key", "ZzriUzE") 93 | data.Add("slug", "welcome") 94 | data.Add("recipient", "root@local.host") 95 | 96 | client := &http.Client{} 97 | r, _ := http.NewRequest("POST", uri, bytes.NewBufferString(data.Encode())) 98 | r.Header.Set("Content-Type", "application/x-www-form-urlencoded") 99 | resp, _ := client.Do(r) 100 | fmt.Println(resp.Body) 101 | } 102 | 103 | 104 | PHP example 105 | ----------- 106 | 107 | .. code-block:: php 108 | 109 | 'ZzriUzE', 'slug' => 'welcome', 'recipient' => 'root@local.host'); 113 | $options = array( 114 | 'http' => array( 115 | 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 116 | 'method' => 'POST', 117 | 'content' => http_build_query($data), 118 | ) 119 | ); 120 | 121 | file_get_contents($url, false, stream_context_create($options)); 122 | 123 | 124 | *using Curl* 125 | 126 | .. code-block:: php 127 | 128 | 'ZzriUzE', 'slug' => 'welcome', 'recipient' => 'root@local.host'); 132 | 133 | $ch = curl_init($url); 134 | curl_setopt($ch, CURLOPT_POST, 1); 135 | curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); 136 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 137 | curl_setopt($ch, CURLOPT_HEADER, 0); 138 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 139 | 140 | curl_exec($ch); 141 | 142 | 143 | Ruby example 144 | ------------ 145 | 146 | .. code-block:: ruby 147 | 148 | require "net/http" 149 | require 'net/https' 150 | require "uri" 151 | 152 | uri = URI.parse("http://127.0.0.1:8000/dbmail/api/") 153 | https = Net::HTTP.new(uri.host,uri.port) 154 | req = Net::HTTP::Post.new(uri.path) 155 | 156 | button = { 157 | "api_key" => "ZzriUzE", 158 | "slug" => "welcome", 159 | "recipient" => "root@local.host" 160 | } 161 | req.set_form_data(button) 162 | https.request(req) 163 | 164 | 165 | Node.js example 166 | --------------- 167 | 168 | .. code-block:: js 169 | 170 | var request = require('request'); 171 | 172 | var uri = 'http://127.0.0.1:8000/dbmail/api/'; 173 | var data = { 174 | api_key: 'ZzriUzE', 175 | slug: 'welcome', 176 | recipient: 'root@local.host' 177 | }; 178 | 179 | request.post({ 180 | headers: {'content-type': 'application/x-www-form-urlencoded'}, 181 | url: uri, form: data 182 | }, function (error, response, body) { 183 | console.log(body); 184 | }); 185 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/commands.rst: -------------------------------------------------------------------------------- 1 | .. _commands: 2 | 3 | Management commands 4 | =================== 5 | 6 | Commands 7 | -------- 8 | ``send_dbmail_deferred_signal`` - Send deferred mails which stored on database (if dbmail.Signals was used). 9 | 10 | ``update_dbmail_cache`` - Best way for update cache after migration to new app version. 11 | 12 | ``clean_dbmail_cache`` - Clear all caches. 13 | 14 | ``clean_dbmail_logs`` - Clear old logs. Days can be defined as DB_MAILER_LOGS_EXPIRE_DAYS constant. 15 | 16 | ``dbmail_test_send`` - Send test mail from command line. For example: 17 | 18 | .. code-block:: bash 19 | 20 | $ ./manage.py dbmail_test_send --email=root@local.host --pk=1 --without-celery 21 | 22 | 23 | Crontab 24 | ------- 25 | 26 | Simple example: 27 | 28 | .. code-block:: bash 29 | 30 | SHELL=/bin/bash 31 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games 32 | MAILTO=root@localhost 33 | PYTHON_BIN=/home/user/example.com/venv/bin/python 34 | MANAGE_PY=/home/user/example.com/www/manage.py 35 | LOG_FILE=/var/log/dbmail.cron.log 36 | 37 | # Project commands 38 | 30 2 * * * $PYTHON_BIN $MANAGE_PY clean_dbmail_logs >> $LOG_FILE 2>&1 39 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-db-mailer documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Jan 18 14:55:29 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys 15 | import os 16 | 17 | # If extensions (or modules to document with autodoc) are in another directory, 18 | # add these directories to sys.path here. If the directory is relative to the 19 | # documentation root, use os.path.abspath to make it absolute, like shown here. 20 | sys.path.insert(0, os.path.abspath('../demo')) 21 | sys.path.append(os.path.abspath('../')) 22 | os.environ['DJANGO_SETTINGS_MODULE'] = 'demo.settings' 23 | 24 | from dbmail import get_version 25 | 26 | # -- General configuration ----------------------------------------------------- 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | #needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be extensions 32 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 33 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo'] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The suffix of source filenames. 39 | source_suffix = '.rst' 40 | 41 | # The encoding of source files. 42 | #source_encoding = 'utf-8-sig' 43 | 44 | # The master toctree document. 45 | master_doc = 'index' 46 | 47 | # General information about the project. 48 | project = u'django-db-mailer' 49 | copyright = u'2014, ' 50 | 51 | # The version info for the project you're documenting, acts as replacement for 52 | # |version| and |release|, also used in various other places throughout the 53 | # built documents. 54 | # 55 | # The short X.Y version. 56 | version = get_version() 57 | # The full version, including alpha/beta/rc tags. 58 | release = get_version() 59 | 60 | # The language for content autogenerated by Sphinx. Refer to documentation 61 | # for a list of supported languages. 62 | #language = None 63 | 64 | # There are two options for replacing |today|: either, you set today to some 65 | # non-false value, then it is used: 66 | #today = '' 67 | # Else, today_fmt is used as the format for a strftime call. 68 | #today_fmt = '%B %d, %Y' 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | exclude_patterns = [] 73 | 74 | # The reST default role (used for this markup: `text`) to use for all documents. 75 | #default_role = None 76 | 77 | # If true, '()' will be appended to :func: etc. cross-reference text. 78 | #add_function_parentheses = True 79 | 80 | # If true, the current module name will be prepended to all description 81 | # unit titles (such as .. function::). 82 | #add_module_names = True 83 | 84 | # If true, sectionauthor and moduleauthor directives will be shown in the 85 | # output. They are ignored by default. 86 | #show_authors = False 87 | 88 | # The name of the Pygments (syntax highlighting) style to use. 89 | pygments_style = 'sphinx' 90 | 91 | # A list of ignored prefixes for module index sorting. 92 | #modindex_common_prefix = [] 93 | 94 | 95 | # -- Options for HTML output --------------------------------------------------- 96 | 97 | # The theme to use for HTML and HTML Help pages. See the documentation for 98 | # a list of builtin themes. 99 | html_theme = 'default' 100 | 101 | # Theme options are theme-specific and customize the look and feel of a theme 102 | # further. For a list of options available for each theme, see the 103 | # documentation. 104 | #html_theme_options = {} 105 | 106 | # Add any paths that contain custom themes here, relative to this directory. 107 | #html_theme_path = [] 108 | 109 | # The name for this set of Sphinx documents. If None, it defaults to 110 | # " v documentation". 111 | #html_title = None 112 | 113 | # A shorter title for the navigation bar. Default is the same as html_title. 114 | #html_short_title = None 115 | 116 | # The name of an image file (relative to this directory) to place at the top 117 | # of the sidebar. 118 | #html_logo = None 119 | 120 | # The name of an image file (within the static path) to use as favicon of the 121 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 122 | # pixels large. 123 | #html_favicon = None 124 | 125 | # Add any paths that contain custom static files (such as style sheets) here, 126 | # relative to this directory. They are copied after the builtin static files, 127 | # so a file named "default.css" will overwrite the builtin "default.css". 128 | #html_static_path = ['_static'] 129 | 130 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 131 | # using the given strftime format. 132 | #html_last_updated_fmt = '%b %d, %Y' 133 | 134 | # If true, SmartyPants will be used to convert quotes and dashes to 135 | # typographically correct entities. 136 | #html_use_smartypants = True 137 | 138 | # Custom sidebar templates, maps document names to template names. 139 | #html_sidebars = {} 140 | 141 | # Additional templates that should be rendered to pages, maps page names to 142 | # template names. 143 | #html_additional_pages = {} 144 | 145 | # If false, no module index is generated. 146 | #html_domain_indices = True 147 | 148 | # If false, no index is generated. 149 | #html_use_index = True 150 | 151 | # If true, the index is split into individual pages for each letter. 152 | #html_split_index = False 153 | 154 | # If true, links to the reST sources are added to the pages. 155 | #html_show_sourcelink = True 156 | 157 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 158 | #html_show_sphinx = True 159 | 160 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 161 | #html_show_copyright = True 162 | 163 | # If true, an OpenSearch description file will be output, and all pages will 164 | # contain a tag referring to it. The value of this option must be the 165 | # base URL from which the finished HTML is served. 166 | #html_use_opensearch = '' 167 | 168 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 169 | #html_file_suffix = None 170 | 171 | # Output file base name for HTML help builder. 172 | htmlhelp_basename = 'django-db-mailerdoc' 173 | 174 | 175 | # -- Options for LaTeX output -------------------------------------------------- 176 | 177 | latex_elements = { 178 | # The paper size ('letterpaper' or 'a4paper'). 179 | #'papersize': 'letterpaper', 180 | 181 | # The font size ('10pt', '11pt' or '12pt'). 182 | #'pointsize': '10pt', 183 | 184 | # Additional stuff for the LaTeX preamble. 185 | #'preamble': '', 186 | } 187 | 188 | # Grouping the document tree into LaTeX files. List of tuples 189 | # (source start file, target name, title, author, documentclass [howto/manual]). 190 | latex_documents = [ 191 | ('index', 'django-db-mailer.tex', u'django-db-mailer Documentation', 192 | u'', 'manual'), 193 | ] 194 | 195 | # The name of an image file (relative to this directory) to place at the top of 196 | # the title page. 197 | #latex_logo = None 198 | 199 | # For "manual" documents, if this is true, then toplevel headings are parts, 200 | # not chapters. 201 | #latex_use_parts = False 202 | 203 | # If true, show page references after internal links. 204 | #latex_show_pagerefs = False 205 | 206 | # If true, show URL addresses after external links. 207 | #latex_show_urls = False 208 | 209 | # Documents to append as an appendix to all manuals. 210 | #latex_appendices = [] 211 | 212 | # If false, no module index is generated. 213 | #latex_domain_indices = True 214 | 215 | 216 | # -- Options for manual page output -------------------------------------------- 217 | 218 | # One entry per manual page. List of tuples 219 | # (source start file, name, description, authors, manual section). 220 | man_pages = [ 221 | ('index', 'django-db-mailer', u'django-db-mailer Documentation', 222 | [u'gotlium'], 1) 223 | ] 224 | 225 | # If true, show URL addresses after external links. 226 | #man_show_urls = False 227 | 228 | 229 | # -- Options for Texinfo output ------------------------------------------------ 230 | 231 | # Grouping the document tree into Texinfo files. List of tuples 232 | # (source start file, target name, title, author, 233 | # dir menu entry, description, category) 234 | texinfo_documents = [ 235 | ('index', 'django-db-mailer', u'django-db-mailer Documentation', 236 | u'gotlium', 'django-db-mailer', 'One line description of project.', 237 | 'Miscellaneous'), 238 | ] 239 | 240 | # Documents to append as an appendix to all manuals. 241 | #texinfo_appendices = [] 242 | 243 | # If false, no module index is generated. 244 | #texinfo_domain_indices = True 245 | 246 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 247 | #texinfo_show_urls = 'footnote' -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/LPgenerator/django-db-mailer/issues. 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | is open to whoever wants to implement it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "feature" 34 | is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | django-db-mailer could always use more documentation, whether as part of the 40 | official django-db-mailer docs, in docstrings, or even on the web in blog posts, 41 | articles, and such. 42 | 43 | Submit Feedback 44 | ~~~~~~~~~~~~~~~ 45 | 46 | The best way to send feedback is to file an issue at https://github.com/LPgenerator/django-db-mailer/issues. 47 | 48 | If you are proposing a feature: 49 | 50 | * Explain in detail how it would work. 51 | * Keep the scope as narrow as possible, to make it easier to implement. 52 | * Remember that this is a volunteer-driven project, and that contributions 53 | are welcome :) 54 | 55 | Get Started! 56 | ------------ 57 | 58 | Ready to contribute? Here's how to set up `django-db-mailer` for local development. 59 | 60 | 1. Fork the `django-db-mailer` repo on GitHub. 61 | 2. Clone your fork locally and switch to development branch:: 62 | 63 | $ git clone git@github.com:LPgenerator/django-db-mailer.git 64 | $ cd django-db-mailer/ 65 | $ git fetch --all 66 | $ git checkout development 67 | 68 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 69 | 70 | $ mkvirtualenv django-db-mailer 71 | $ workon django-db-mailer 72 | $ python setup.py develop 73 | 74 | 4. Create a branch for local development:: 75 | 76 | $ git checkout -b name-of-your-bugfix-or-feature 77 | 78 | Now you can make your changes locally. 79 | 80 | 5. When you're done making changes, check that your changes pass flake8 and the 81 | tests, including testing other Python versions with tox:: 82 | 83 | $ make pep8 84 | $ make test 85 | $ make tox 86 | 87 | To get flake8 and tox, just pip install them into your virtualenv. 88 | 89 | 6. Commit your changes and push your branch to GitHub:: 90 | 91 | $ git add . 92 | $ git commit -m "Your detailed description of your changes." 93 | $ git push origin name-of-your-bugfix-or-feature 94 | 95 | 7. Submit a pull request through the GitHub website. 96 | 97 | Pull Request Guidelines 98 | ----------------------- 99 | 100 | Before you submit a pull request, check that it meets these guidelines: 101 | 102 | 1. The pull request should include tests. 103 | 2. If the pull request adds functionality, the docs should be updated. Put 104 | your new functionality into a function with a docstring, and add the 105 | feature to the list in README.rst. 106 | 3. The pull request should work for Python 2.6, 2.7 and for PyPy. Check 107 | https://travis-ci.org/LPgenerator/django-db-mailer/pull_requests 108 | and make sure that the tests pass for all supported Python versions. 109 | -------------------------------------------------------------------------------- /docs/custom_backends_and_providers.rst: -------------------------------------------------------------------------------- 1 | Custom backends and providers 2 | ============================= 3 | 4 | 5 | Double backend example 6 | ---------------------- 7 | 8 | A simple example of sending alerts by SMS and TTS. 9 | 10 | 11 | .. code-block:: python 12 | 13 | 14 | from dbmail.backends.sms import Sender as SmsSender 15 | from dbmail.backends.tts import Sender as TtsSender 16 | 17 | 18 | class Sender(object): 19 | def __init__(self, *args, **kwargs): 20 | self.args = args 21 | self.kwargs = kwargs 22 | 23 | def send(self, is_celery=True): 24 | SmsSender(*self.args, **self.kwargs).send(is_celery) 25 | TtsSender(*self.args, **self.kwargs).send(is_celery) 26 | return 'OK' 27 | 28 | 29 | class SenderDebug(Sender): 30 | def send(self, is_celery=True): 31 | print(self.args) 32 | print(self.kwargs) 33 | print({'is_celery': is_celery}) 34 | 35 | 36 | def send_double_notification(*args, **kwargs): 37 | from dbmail import db_sender 38 | 39 | kwargs['backend'] = 'demo.custom_backends.double' 40 | db_sender(*args, **kwargs) 41 | 42 | 43 | 44 | Let's try: 45 | 46 | 47 | .. code-block:: python 48 | 49 | 50 | from demo.custom_backends.double import send_double_notification 51 | 52 | send_double_notification('welcome', '+79031234567') 53 | 54 | 55 | 56 | Slack backend example 57 | --------------------- 58 | 59 | You're own backend, which send message to Slack channel. 60 | 61 | 62 | .. code-block:: python 63 | 64 | 65 | from dbmail.backends.mail import Sender as SenderBase 66 | from dbmail import import_module 67 | 68 | 69 | class Sender(SenderBase): 70 | """ 71 | Specify new backend when you want to change standard backends behavior 72 | More examples you can find at ./dbmail/backends directory 73 | """ 74 | 75 | # you're custom provider will be defined here. 76 | # now we use standard provider 77 | provider = 'dbmail.providers.slack.push' 78 | 79 | # channels/recipients processing 80 | def _get_recipient_list(self, recipient): 81 | if isinstance(recipient, list): 82 | return recipient 83 | return map(lambda x: x.strip(), recipient.split(',')) 84 | 85 | # send message 86 | def _send(self): 87 | module = import_module(self.provider) 88 | for recipient in self._recipient_list: 89 | module.send(recipient, self._message) 90 | 91 | 92 | class SenderDebug(Sender): 93 | """ 94 | Print message to stdout when DEBUG is True 95 | """ 96 | def _send(self): 97 | self.debug('Message', self._message) 98 | 99 | 100 | # helper function, which will be used on code 101 | def send_db_slack(slug, *args, **kwargs): 102 | from dbmail import db_sender 103 | 104 | kwargs['backend'] = 'demo.custom_backends.slack' 105 | db_sender(slug, *args, **kwargs) 106 | 107 | 108 | Slack settings 109 | 110 | 111 | .. code-block:: python 112 | 113 | 114 | SLACK_USERNAME = 'robot' 115 | SLACK_HOOCK_URL = 'https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX' 116 | SLACK_CHANNEL = 'main' 117 | 118 | 119 | Let's try: 120 | 121 | 122 | .. code-block:: python 123 | 124 | from demo.custom_backends.slack import send_db_slack 125 | 126 | send_db_slack('welcome', {'username': 'GoTLiuM'}) 127 | 128 | 129 | 130 | Own provider 131 | ------------ 132 | 133 | Create new file which will be implemented simple function below 134 | 135 | 136 | .. code-block:: python 137 | 138 | def send(recipient, message, **kwargs): 139 | # some named args from send_db function 140 | custom_field = kwargs.pop('my_field', 'default value') 141 | ... 142 | # Some part of code, which will be send message over some protocol 143 | ... 144 | return True 145 | 146 | 147 | Add necessary settings into settings.py 148 | 149 | 150 | .. code-block:: python 151 | 152 | SOME_URL = '...' 153 | 154 | 155 | Configure one of built-in backend to use your own provider 156 | 157 | 158 | .. code-block:: python 159 | 160 | DB_MAILER_SMS_PROVIDER = 'apps.sms' 161 | DB_MAILER_TTS_PROVIDER = 'apps.tts' 162 | DB_MAILER_PUSH_PROVIDER = 'apps.push' 163 | 164 | 165 | or write own function to use your provider 166 | 167 | 168 | .. code-block:: python 169 | 170 | def send_over_own_provider(slug, *args, **kwargs): 171 | from dbmail import db_sender 172 | 173 | # can be one of built-in, or custom backend 174 | # kwargs['backend'] = 'dbmail.backends.sms' 175 | kwargs['provider'] = 'apps.sms' 176 | db_sender(slug, *args, **kwargs) 177 | -------------------------------------------------------------------------------- /docs/demo.rst: -------------------------------------------------------------------------------- 1 | .. _demo: 2 | 3 | Demo installation 4 | ================= 5 | 6 | 7 | Docker 8 | ------ 9 | 10 | .. code-block:: bash 11 | 12 | $ git clone --depth 1 -b master https://github.com/LPgenerator/django-db-mailer.git db-mailer 13 | $ cd db-mailer 14 | $ docker build -t dbmail . 15 | $ docker run -it -d -p 8000:8000 --name dbmail dbmail 16 | $ docker exec -i -t dbmail /bin/bash 17 | $ cd /mailer/ 18 | 19 | 20 | Vagrant 21 | ------- 22 | 23 | .. code-block:: bash 24 | 25 | $ git clone --depth 1 -b master https://github.com/LPgenerator/django-db-mailer.git db-mailer 26 | $ cd db-mailer 27 | $ vagrant up --provider virtualbox 28 | $ vagrant ssh 29 | $ cd /mailer/ 30 | 31 | 32 | OS X/Linux 33 | ---------- 34 | 35 | .. code-block:: bash 36 | 37 | $ sudo apt-get install -y virtualenvwrapper redis-server git python-dev libxml2-dev libxslt-dev zlib1g-dev || brew install pyenv-virtualenvwrapper redis git 38 | $ source /usr/share/virtualenvwrapper/virtualenvwrapper.sh || source /usr/local/bin/virtualenvwrapper.sh 39 | $ mkvirtualenv db-mailer 40 | $ workon db-mailer 41 | $ git clone --depth 1 https://github.com/LPgenerator/django-db-mailer.git db-mailer 42 | $ cd db-mailer 43 | $ python setup.py develop 44 | $ cd demo 45 | $ pip install -r requirements.txt 46 | $ python manage.py migrate --noinput 47 | $ python manage.py createsuperuser --username admin --email admin@local.host 48 | $ redis-server >& /dev/null & 49 | $ python manage.py runserver >& /dev/null & 50 | $ python manage.py celeryd -Q default >& /dev/null & 51 | 52 | 53 | Demo scenario 54 | ------------- 55 | 56 | Open Shell: 57 | 58 | .. code-block:: bash 59 | 60 | $ python manage.py shell_plus --print-sql 61 | 62 | 63 | Create new template: 64 | 65 | .. code-block:: python 66 | 67 | from dbmail.models import MailTemplate 68 | from dbmail import send_db_mail 69 | 70 | MailTemplate.objects.create( 71 | name="Site welcome template", 72 | subject="Welcome", 73 | message="Welcome to our site. We are glad to see you.", 74 | slug="welcome", 75 | is_html=False, 76 | ) 77 | 78 | 79 | Try to send test email with created template (without celery): 80 | 81 | .. code-block:: python 82 | 83 | send_db_mail('welcome', 'user@example.com', use_celery=False) 84 | 85 | 86 | Send email using celery: 87 | 88 | .. code-block:: python 89 | 90 | send_db_mail('welcome', 'user@example.com') 91 | 92 | 93 | Check mail logs: 94 | 95 | .. code-block:: python 96 | 97 | from pprint import pprint 98 | from django.forms.models import model_to_dict 99 | from dbmail.models import MailLog 100 | 101 | pprint([model_to_dict(obj) for obj in MailLog.objects.all()]) 102 | 103 | 104 | Open app in browser (login and password is admin/admin): 105 | 106 | .. code-block:: bash 107 | 108 | $ xdg-open http://127.0.0.1:8000/admin/dbmail/ >& /dev/null || open http://127.0.0.1:8000/admin/dbmail/ >& /dev/null 109 | -------------------------------------------------------------------------------- /docs/dev_install.rst: -------------------------------------------------------------------------------- 1 | Installation for development 2 | ============================ 3 | 4 | Installation 5 | ------------ 6 | 7 | Install all required packages and configure your project and environment: 8 | 9 | .. code-block:: bash 10 | 11 | $ sudo apt-get install -y virtualenvwrapper redis-server git python-dev libxml2-dev libxslt-dev zlib1g-dev || brew install pyenv-virtualenvwrapper redis git 12 | $ source /usr/share/virtualenvwrapper/virtualenvwrapper.sh || source /usr/local/bin/virtualenvwrapper.sh 13 | $ mkvirtualenv db-mailer 14 | $ workon db-mailer 15 | $ git clone --depth 1 https://github.com/LPgenerator/django-db-mailer.git db-mailer 16 | $ cd db-mailer 17 | $ python setup.py develop 18 | $ cd demo 19 | $ pip install -r requirements.txt 20 | $ python manage.py migrate --noinput 21 | $ python manage.py createsuperuser --username admin --email admin@local.host 22 | $ redis-server >& /dev/null & 23 | $ ln -sf /bin/bash /bin/sh 24 | $ python manage.py runserver >& /dev/null & 25 | $ python manage.py celeryd -Q default >& /dev/null & 26 | $ python manage.py shell_plus --print-sql 27 | 28 | 29 | Examples 30 | -------- 31 | Simple test from command line: 32 | 33 | .. code-block:: python 34 | 35 | >>> from dbmail.models import MailTemplate, MailGroup, MailGroupEmail, MailLog 36 | >>> from dbmail import send_db_mail 37 | 38 | >>> MailTemplate.objects.create( 39 | name="Site welcome template", 40 | subject="Welcome", 41 | message="Welcome to our site. We are glad to see you.", 42 | slug="welcome", 43 | is_html=False, 44 | ) 45 | 46 | >>> group = MailGroup.objects.create( 47 | name="Site admins", 48 | slug="administrators", 49 | ) 50 | >>> MailGroupEmail.objects.bulk_create([ 51 | MailGroupEmail(name="Admin 1", email="admin1@example.com", group=group), 52 | MailGroupEmail(name="Admin 2", email="admin2@example.com", group=group), 53 | ]) 54 | 55 | >>> # test simple string 56 | >>> send_db_mail('welcome', 'root@localhost') 57 | 58 | >>> # test emails list 59 | >>> send_db_mail('welcome', ['user1@example.com', 'user2@example.com']) 60 | 61 | >>> # test internal groups 62 | >>> send_db_mail('welcome', 'administrators') 63 | 64 | >>> # test without celery 65 | >>> send_db_mail('welcome', 'administrators', use_celery=False) 66 | 67 | >>> # Show what stored in logs 68 | >>> print MailLog.objects.all().count() 69 | 70 | 71 | Make targets 72 | ------------ 73 | Simple shortcuts for fast development 74 | 75 | | ``clean`` - Clean temporary files 76 | | ``clean-celery`` - Clean all celery queues 77 | | ``pep8`` - Check code for pep8 rules 78 | | ``sphinx`` - Make app docs 79 | | ``run`` - Run Django development server 80 | | ``run-celery`` - Run celery daemon 81 | | ``shell`` - Run project shell 82 | | ``run-redis`` - Run Redis daemon 83 | | ``help`` - Display callable targets 84 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | django-db-mailer 2 | ================ 3 | 4 | Django module to easily send emails using django templates stored on database. 5 | 6 | Contents 7 | -------- 8 | 9 | .. toctree:: 10 | :maxdepth: 1 11 | 12 | installation 13 | settings 14 | usage 15 | signals 16 | conf 17 | api 18 | commands 19 | web_push 20 | custom_backends_and_providers 21 | providers 22 | demo 23 | dev_install 24 | contributing 25 | authors 26 | 27 | Contributing 28 | ------------ 29 | 30 | You can grab latest version of code on ``master`` branch at Github_. 31 | 32 | Feel free to submit issues_, pull requests are also welcome. 33 | 34 | Good contributions follow :ref:`simple guidelines ` 35 | 36 | .. _Github: https://github.com/LPgenerator/django-db-mailer 37 | .. _issues: https://github.com/LPgenerator/django-db-mailer/issues 38 | 39 | .. |br| raw:: html 40 | 41 |
    42 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Compatibility 5 | ------------- 6 | 7 | * Python: 2.7, pypy2.7, 3.4, 3.5, 3.6, 3.7, pypy3.5 8 | * Django: 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10, 1.11, 2.0, 2.1 9 | 10 | 11 | Installation 12 | ------------ 13 | 14 | Recommended way to install is via pip: 15 | 16 | .. code-block:: bash 17 | 18 | $ pip install django-db-mailer 19 | 20 | # do not forget collect your project static on production servers 21 | $ python manage.py collectstatic 22 | 23 | 24 | .. _basic: 25 | 26 | Settings configuration 27 | ---------------------- 28 | 29 | Add ``dbmail`` and ``django.contrib.sites`` to ``INSTALLED_APPS`` in the settings.py: 30 | 31 | .. code-block:: python 32 | 33 | INSTALLED_APPS = ( 34 | ... 35 | 'django.contrib.sites', 36 | 'dbmail', 37 | ... 38 | ) 39 | SITE_ID = 1 40 | 41 | 42 | DB initialization 43 | ----------------- 44 | 45 | Create application tables on database: 46 | 47 | 48 | .. code-block:: bash 49 | 50 | $ python manage.py migrate 51 | -------------------------------------------------------------------------------- /docs/providers.rst: -------------------------------------------------------------------------------- 1 | .. _providers: 2 | 3 | Providers examples 4 | ================== 5 | 6 | 7 | Apple APNs/APNs2 8 | ---------------- 9 | 10 | .. code-block:: python 11 | 12 | send_db_push( 13 | 'welcome', 14 | 'device-token', 15 | { 16 | 'name': 'User' 17 | }, 18 | provider='dbmail.providers.apple.apns', 19 | 20 | # ios specific 21 | category='NEW_MESSAGE_CATEGORY', 22 | content_available=0, 23 | sound='default', 24 | badge=6 25 | ) 26 | 27 | 28 | Google GCM 29 | ---------- 30 | 31 | .. code-block:: python 32 | 33 | send_db_push( 34 | 'welcome', 35 | 'user-id', 36 | { 37 | 'name': 'User' 38 | }, 39 | provider='dbmail.providers.google.android', 40 | 41 | # android specific 42 | vibrationPattern=[2000, 1000, 500, 500], 43 | ledColor=[0, 0, 73, 31], 44 | priority=2, 45 | msgcnt=2, 46 | notId=121, 47 | ) 48 | 49 | 50 | Microsoft Tile 51 | -------------- 52 | 53 | .. code-block:: python 54 | 55 | send_db_push( 56 | 'welcome', 57 | 'http://s.notify.live.net/u/1/sin/...', 58 | { 59 | 'name': 'User' 60 | }, 61 | provider='dbmail.providers.microsoft.tile', 62 | 63 | # MS specific 64 | template="tile", 65 | id="SecondaryTile.xaml?DefaultTitle=FromTile", 66 | count="5", 67 | back_background_image="http://background.com/back", 68 | background_image="http://background.images.com/background'", 69 | back_title="back title", 70 | back_content="back content here", 71 | event="title", # instead title (configured on settings) 72 | ) 73 | 74 | 75 | Microsoft Toast 76 | --------------- 77 | 78 | .. code-block:: python 79 | 80 | send_db_push( 81 | 'welcome', 82 | 'http://s.notify.live.net/u/1/sin/...', 83 | { 84 | 'name': 'User' 85 | }, 86 | provider='dbmail.providers.microsoft.toast', 87 | 88 | # MS specific 89 | sound='', 90 | param='/Page2.xaml?NavigatedFrom=Toast Notification', 91 | path='/Views/MainScreen.xaml', 92 | event="title", # instead title (configured on settings) 93 | ) 94 | 95 | 96 | HTTP Push 97 | --------- 98 | 99 | .. code-block:: python 100 | 101 | send_db_push( 102 | 'welcome', 103 | 'http://localhost/receiver/', 104 | { 105 | 'name': 'User' 106 | }, 107 | provider='dbmail.providers.http.push', 108 | 109 | # Not limited args 110 | event='registration', 111 | uid='12345', 112 | ) 113 | 114 | 115 | Centrifugo Push 116 | --------------- 117 | 118 | .. code-block:: python 119 | 120 | send_db_push( 121 | 'welcome', 122 | 'users', 123 | { 124 | 'name': 'User' 125 | }, 126 | provider='dbmail.providers.centrifugo.push', 127 | 128 | # Not limited args 129 | event='registration', 130 | uid='12345', 131 | ) 132 | 133 | 134 | PushAll Service 135 | --------------- 136 | 137 | .. code-block:: python 138 | 139 | send_db_push( 140 | 'welcome', 141 | 'broadcast', 142 | { 143 | 'name': 'User' 144 | }, 145 | provider='dbmail.providers.pushall.push', 146 | 147 | # Not limited args 148 | title='MyApp', 149 | # uid='12345', # only for unicast 150 | # icon='example.com/icon.png', 151 | # url='example.com', 152 | # hidden=0, 153 | # encode='utf8', 154 | # priority=1, 155 | # ttl=86400, 156 | ) 157 | -------------------------------------------------------------------------------- /docs/settings.rst: -------------------------------------------------------------------------------- 1 | .. _settings: 2 | 3 | Settings 4 | ======== 5 | 6 | Required Settings 7 | ----------------- 8 | 9 | For minimize requests to database, configure django caches: 10 | 11 | .. code-block:: bash 12 | 13 | $ pip install redis django-pylibmc 14 | # or 15 | $ pip install redis django-redis 16 | 17 | .. code-block:: python 18 | 19 | # settings.py 20 | CACHES = { 21 | # Memcached 22 | 'default': { 23 | 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 24 | }, 25 | # or Redis 26 | "default": { 27 | 'BACKEND': 'redis_cache.cache.RedisCache', 28 | 'LOCATION': '127.0.0.1:6379:2', 29 | }, 30 | # or Memory 31 | "default": { 32 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 33 | 'LOCATION': 'unique-snowflake' 34 | }, 35 | } 36 | 37 | *Note: App do not work without caches* 38 | 39 | 40 | Configure project default SMTP settings:: 41 | 42 | # settings.py 43 | EMAIL_HOST = 'smtp.gmail.com' 44 | EMAIL_PORT = 587 45 | EMAIL_HOST_USER = 'noreply@gmail.com' 46 | EMAIL_HOST_PASSWORD = 'your-password' 47 | EMAIL_USE_TLS = True 48 | DEFAULT_FROM_EMAIL = 'User ' 49 | 50 | 51 | Also you can configure smtp options for dbmail on the admin interface. But maybe other apps, 52 | like ``django-registration`` is used default project settings. 53 | 54 | 55 | Optional Settings 56 | ----------------- 57 | 58 | Install ``redis-server``, and configure ``django-celery`` for use priorities and scheduler: 59 | 60 | .. code-block:: bash 61 | 62 | $ pip install redis django-celery 63 | 64 | 65 | .. code-block:: python 66 | 67 | # settings.py 68 | 69 | import djcelery 70 | import sys 71 | 72 | INSTALLED_APPS += ('djcelery',) 73 | 74 | BROKER_URL = 'redis://127.0.0.1:6379/1' 75 | 76 | CELERY_ACKS_LATE = True 77 | CELERYD_PREFETCH_MULTIPLIER = 1 78 | 79 | # use priority steps only for mail queue 80 | if 'mail_messages' in sys.argv: 81 | BROKER_TRANSPORT_OPTIONS = { 82 | 'priority_steps': list(range(10)), 83 | } 84 | 85 | CELERY_TASK_SERIALIZER = 'pickle' 86 | CELERY_DEFAULT_QUEUE = 'default' # use mail_messages, if workers is divided 87 | 88 | djcelery.setup_loader() 89 | 90 | 91 | .. code-block:: bash 92 | 93 | $ python manage.py celeryd --loglevel=debug -Q default 94 | $ python manage.py celeryd --loglevel=info -Q mail_messages -n mail_messages # divide workers and queues on production 95 | 96 | 97 | *Note: Do not forget define on command line queue name.* 98 | 99 | 100 | ``django-db-mailer`` can work without any third-party apps, but if you want to use all 101 | available app features and send emails on the background with priorities and scheduler, 102 | you need configure some apps, which will be pretty for your project and your clients. 103 | 104 | 105 | **Templates Revision**: 106 | 107 | .. code-block:: bash 108 | 109 | $ pip install django-reversion 110 | 111 | .. code-block:: python 112 | 113 | # settings.py 114 | INSTALLED_APPS += ('reversion',) 115 | 116 | Find information about compatibility with your Django versions `here `_. 117 | 118 | 119 | **Templates Compare Revision**: 120 | 121 | .. code-block:: bash 122 | 123 | $ pip install django-reversion-compare diff-match-patch 124 | 125 | .. code-block:: python 126 | 127 | # settings.py 128 | INSTALLED_APPS += ('reversion', 'reversion_compare',) 129 | 130 | 131 | ``django-reversion-compare`` is not compatible at this time with Django 1.4+, 132 | but you can override ``django-reversion-compare`` templates on your project templates, 133 | and app will be work with Django 1.4+. 134 | 135 | 136 | **Editor**: 137 | 138 | .. code-block:: bash 139 | 140 | $ pip install django-tinymce 141 | # OR 142 | $ pip install django-ckeditor 143 | 144 | .. code-block:: python 145 | 146 | # settings.py 147 | INSTALLED_APPS += ('tinymce',) 148 | TINYMCE_DEFAULT_CONFIG = { 149 | 'plugins': "table,spellchecker,paste,searchreplace", 150 | 'theme': "advanced", 151 | 'cleanup_on_startup': True, 152 | 'custom_undo_redo_levels': 10, 153 | } 154 | # urls.py 155 | urlpatterns += patterns( 156 | '', url(r'^tinymce/', include('tinymce.urls')), 157 | ) 158 | 159 | 160 | **Premailer**: 161 | 162 | .. code-block:: bash 163 | 164 | $ pip install premailer 165 | 166 | That's all what you need. App for turns CSS blocks into style attributes. Very pretty for cross-clients html templates. 167 | 168 | 169 | **Theme**: 170 | 171 | .. code-block:: bash 172 | 173 | $ pip install django-grappelli 174 | 175 | ``django-db-mailer`` supported from box ``django-grappelli`` and ``django-suit`` skin. Information about compatibility available `here `_. 176 | 177 | 178 | **Translation Support**: 179 | 180 | .. code-block:: bash 181 | 182 | $ pip install django-modeltranslation 183 | 184 | .. code-block:: python 185 | 186 | # settings.py 187 | MODELTRANSLATION_DEFAULT_LANGUAGE = 'en' 188 | MODELTRANSLATION_LANGUAGES = ('ru', 'en') 189 | MODELTRANSLATION_TRANSLATION_FILES = ( 190 | 'dbmail.translation', 191 | ) 192 | INSTALLED_APPS = ('modeltranslation',) + INSTALLED_APPS 193 | 194 | # If you are using django-grappelli, add grappelli_modeltranslation to the settings 195 | INSTALLED_APPS = ( 196 | 'grappelli', 197 | 'grappelli_modeltranslation', 198 | 'modeltranslation', 199 | ) + INSTALLED_APPS 200 | 201 | .. code-block:: bash 202 | 203 | $ ./manage.py collectstatic 204 | 205 | 206 | Update dbmail fields: 207 | 208 | .. code-block:: bash 209 | 210 | $ ./manage.py sync_translation_fields --noinput 211 | 212 | 213 | **Tracking**: 214 | 215 | .. code-block:: bash 216 | 217 | $ pip install httpagentparser django-ipware geoip2 218 | 219 | If you use Django 1.8, you should install `geoip` package instead of `geoip2`. 220 | 221 | 222 | Add url patterns into urls.py: 223 | 224 | .. code-block:: python 225 | 226 | urlpatterns += patterns( 227 | '', url(r'^dbmail/', include('dbmail.urls')), 228 | ) 229 | 230 | 231 | Enable tracking and logging on settings: 232 | 233 | .. code-block:: python 234 | 235 | DB_MAILER_TRACK_ENABLE = True 236 | DB_MAILER_ENABLE_LOGGING = True 237 | 238 | 239 | For track information about user, or about mail is read, you must be enable logging, and enable tracking on settings. 240 | Tracking templates must be HTML, not TXT. Celery workers must be launched, if celery is enabled. 241 | Django ``sites`` framework must be configured properly and have a real domain name record. 242 | ``LibGeoIP`` and ``MaxMind`` database must be installed and properly configured. 243 | To debug, open raw message and you can see html which specified on ``DB_MAILER_TRACK_HTML``. 244 | -------------------------------------------------------------------------------- /docs/signals.rst: -------------------------------------------------------------------------------- 1 | .. _signals: 2 | 3 | Signals 4 | ======= 5 | 6 | Signals on database is a native Django signals. 7 | 8 | Available variables for rules on Signals: 9 | 10 | .. code-block:: html 11 | 12 | {{ date }} - current date 13 | {{ date_time }} - current datetime 14 | {{ site }} - current site 15 | {{ domain }} - current site domain 16 | {{ old_instance }} - old instance for pre_save 17 | {{ current_instance }} - available when Update model state is enabled 18 | {{ instance }} - instance from received signal 19 | {{ ... }} - all instance fields as vars 20 | 21 | When all signals was configured, you need to reload your wsgi application. 22 | Auto-reloading can be configured on settings by WSGI_AUTO_RELOAD/UWSGI_AUTO_RELOAD. 23 | But if you launch application on several instances, do it manually. 24 | 25 | **Note:** Don't use a big intervals, if deferred tasks on queue more than 3-4k. It can crash a celery worker. 26 | 27 | For deferred tasks best way is a crontab command + database queue. 28 | You can add ``DB_MAILER_SIGNAL_DEFERRED_DISPATCHER = 'database'`` into project settings, 29 | and call crontab command ``send_dbmail_deferred_signal``. 30 | 31 | 32 | Sending signals 33 | --------------- 34 | 35 | .. code-block:: python 36 | 37 | from dbmail.exceptions import StopSendingException 38 | from dbmail.signals import pre_send, post_send 39 | from dbmail.backends.sms import Sender as SmsSender 40 | 41 | 42 | def check_balance(*args, **kwargs): 43 | # "if" condition is unnecessary due to the way we connect signal to handler 44 | if kwargs['instance']._backend.endswith('sms'): 45 | balance = ... 46 | if balance == 0: 47 | raise StopSendingException 48 | 49 | def decrease_balance(*args, **kwargs): 50 | # decrease user balance 51 | pass 52 | 53 | 54 | pre_send.connect(check_balance, sender=SmsSender) 55 | post_send.connect(decrease_balance, sender=SmsSender) 56 | 57 | 58 | When you want transmit some **kwargs to signal, you can use `signals_kwargs`. 59 | 60 | .. code-block:: python 61 | 62 | from dbmail import send_db_mail 63 | 64 | send_db_mail( 65 | 'welcome', 'user@example.com', 66 | signals_kwargs={'user': User.objects.get(pk=1)}, use_celery=False 67 | ) 68 | 69 | 70 | When using MailSubscriptionAbstract model, you may want to check instance for uniqueness before creating it. 71 | In this case you should use either model meta option "unique-together" 72 | or use pre-save signal, that raises IntegrityError in case of duplicates. 73 | 74 | .. code-block:: python 75 | 76 | from django.db import models 77 | 78 | def check_address_is_unique(sender, instance, **kwargs): 79 | if not (instance.is_checked and instance.is_enabled): 80 | return 81 | 82 | query = sender.objects.filter( 83 | is_checked=True, 84 | is_enabled=True, 85 | address=instance.address, 86 | backend=instance.backend 87 | ) 88 | if instance.pk: 89 | query = query.exclude(pk=instance.pk) 90 | if query.exists(): 91 | raise IntegrityError('address must be unique') 92 | 93 | models.signals.pre_save.connect( 94 | check_address_is_unique, sender=MailSubscription) -------------------------------------------------------------------------------- /requirements/docs.txt: -------------------------------------------------------------------------------- 1 | Django>1.8,<1.9 2 | django-db-mailer 3 | -------------------------------------------------------------------------------- /requirements/package.txt: -------------------------------------------------------------------------------- 1 | Django>1.8,<=2.1 2 | -------------------------------------------------------------------------------- /requirements/tests.txt: -------------------------------------------------------------------------------- 1 | flake8>=2.4.1 2 | coverage==3.7.1 3 | coveralls==0.3 4 | django-tinymce>=1.5.3 5 | -------------------------------------------------------------------------------- /screenshots/apikey_changelist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/apikey_changelist.jpg -------------------------------------------------------------------------------- /screenshots/apps_browse_vars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/apps_browse_vars.jpg -------------------------------------------------------------------------------- /screenshots/apps_view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/apps_view.jpg -------------------------------------------------------------------------------- /screenshots/base_template_changelist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/base_template_changelist.jpg -------------------------------------------------------------------------------- /screenshots/bcc_changelist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/bcc_changelist.jpg -------------------------------------------------------------------------------- /screenshots/group_change.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/group_change.jpg -------------------------------------------------------------------------------- /screenshots/signal_edit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/signal_edit.jpg -------------------------------------------------------------------------------- /screenshots/signals_changelist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/signals_changelist.jpg -------------------------------------------------------------------------------- /screenshots/smtp_changelist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/smtp_changelist.jpg -------------------------------------------------------------------------------- /screenshots/subscriptions_change.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/subscriptions_change.jpg -------------------------------------------------------------------------------- /screenshots/subscriptions_changelist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/subscriptions_changelist.jpg -------------------------------------------------------------------------------- /screenshots/template_compare.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/template_compare.jpg -------------------------------------------------------------------------------- /screenshots/template_edit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/template_edit.jpg -------------------------------------------------------------------------------- /screenshots/template_log_changelist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/template_log_changelist.jpg -------------------------------------------------------------------------------- /screenshots/template_log_view.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/template_log_view.jpg -------------------------------------------------------------------------------- /screenshots/templates_changelist.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/templates_changelist.jpg -------------------------------------------------------------------------------- /screenshots/tracking_edit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LPgenerator/django-db-mailer/858cd4a9b357d30b36cfada785f9dbb279c69ecd/screenshots/tracking_edit.jpg -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from dbmail import get_version 3 | 4 | 5 | setup( 6 | name='django-db-mailer', 7 | version=get_version(), 8 | description='Django module to easily send emails using ' 9 | 'django templates stored in a database.', 10 | keywords="django db mail email html text tts sms push templates mailer", 11 | long_description=open('README.rst').read(), 12 | author="GoTLiuM InSPiRiT", 13 | author_email='gotlium@gmail.com', 14 | url='http://github.com/LPgenerator/django-db-mailer/', 15 | packages=find_packages(exclude=['demo']), 16 | package_data={'dbmail': [ 17 | 'locale/*/LC_MESSAGES/django.*', 18 | 'static/dbmail/admin/js/*.js', 19 | 'fixtures/*.json', 20 | ]}, 21 | include_package_data=True, 22 | install_requires=[ 23 | 'setuptools', 24 | ], 25 | zip_safe=False, 26 | classifiers=[ 27 | 'Development Status :: 5 - Production/Stable', 28 | 'Environment :: Web Environment', 29 | 'Framework :: Django', 30 | 'Intended Audience :: Developers', 31 | 'License :: OSI Approved :: GNU General Public License (GPL)', 32 | 'Operating System :: OS Independent', 33 | 'Programming Language :: Python', 34 | 'Programming Language :: Python :: 2', 35 | 'Programming Language :: Python :: 2.7', 36 | 'Programming Language :: Python :: 3', 37 | 'Programming Language :: Python :: 3.4', 38 | 'Programming Language :: Python :: 3.5', 39 | 'Programming Language :: Python :: 3.6', 40 | 'Programming Language :: Python :: 3.7', 41 | 'Topic :: Internet :: WWW/HTTP', 42 | 'Topic :: Software Development :: Libraries :: Python Modules', 43 | ], 44 | ) 45 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | pep8, 4 | {py27,py34,py35,py36,pypy27,pypy35}-django18, 5 | {py27,py34,py35,py36,pypy27,pypy35}-django111, 6 | {py34,py35,py36,pypy27,pypy35}-django20, 7 | {py35,py36,pypy35}-django21, 8 | coverage 9 | 10 | 11 | [testenv] 12 | usedevelop = True 13 | recreate = False 14 | deps = 15 | django18: Django>=1.8,<1.9 16 | django111: Django>=1.11,<2.0 17 | django20: Django>=2.0,<2.1 18 | django21: Django>=2.1,<2.2 19 | commands = 20 | make test 21 | 22 | 23 | [testenv:pep8] 24 | deps = 25 | flake8==2.4.1 26 | commands = 27 | flake8 --ignore=E402,E731,F401,F811 --exclude=migrations dbmail 28 | 29 | 30 | [testenv:coverage] 31 | deps = 32 | coverage==3.7.1 33 | django >= 2.0, < 2.1 34 | django-redis==3.6.2 35 | commands = 36 | coverage run --branch --source=dbmail ./demo/manage.py test dbmail 37 | coverage report --omit="dbmail/test*,dbmail/migrations*" 38 | --------------------------------------------------------------------------------