Git Product home page Git Product logo

django-drip-campaigns's Introduction

Maintainability Test Coverage License: MIT

Django Drip Campaigns

Build Status

Drip campaigns are pre-written sets of emails sent to customers or prospects over time. Django Drips lets you use the admin to manage drip campaign emails using querysets on Django's User model.

This project is a fork of the one written by Zapier.

You can check out the docs here.

Installation:

  1. Install django-drip-campaings using pip:
pip install django-drip-campaigns
  1. Add 'drip' to your INSTALLED_APPS list on your settings.
INSTALLED_APPS = [
    'django.contrib.contenttypes',
    'django.contrib.comments',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.admin',

    # ...

    'drip',
]
  1. (Optional) Set DRIP_FROM_EMAIL = '<your_app_from_email>' in your settings, where <your_app_from_email> is the email account that is going to be shown in the sent emails. Otherwise EMAIL_HOST_USER value will be used.

  2. Finally, run python manage.py migrate drip to set up the necessary database tables.

Usage

If you haven't, create a superuser with the Django createsuperuser command. Login with the admin user, and select Drips to manage them. You will be able to:

  • View created drips.
  • Create a new drip.
  • Select and delete drips.

Now you can also manage campaigns, select Campaigns to manage them. You will be able to:

  • View created campaigns.
  • Create a new campaign.
  • Select and delete campaign.

Create Campaign

In the Django admin, after select Campaigns, you can click on ADD CAMPAIGN + button to create a new one. You will see the add campaign page:

Add Campaign

When you create a campaign, you need to decide if the related drips will be deleted along with the campaign, using the Delete drips field.

Here you will find an inline creation or edition for Drips this will not include the QUERY SET RULES section. It will only allow you to change the mail content in the Drip.

Campaigns will allow you to manage many Drips that need to be related between them.

Create Drip

In the Django admin, after select Drips, you can click on ADD DRIP + button to create a new one. You will see the add drip page:

Add Drip

Here you can relate the Drip to the corresponding Campaign. Grouping several drips under a campaign.

On the FIELD NAME OF USER input, when you click on it, you will be able to view:

  • The fields of your user's model.
  • The fields of your user's model in other models that are related with it.

Please take a look a this example:

Lookup fields

With this, you can select one or more fields to create useful drips. Additionally if you select a field name of user that has a date type, you can enter in the FIELD VALUE input, a date value written in natural language that combines operations on the current datetime. For example, if you have selected the field last_login that has a date type, and you want to create a drip to send emails to the users who logged in exactly one week ago; you can enter:

now-1 week

or

now- 1 w

Possible operations and values:

  • Add (+) or subtract (-) dates.
  • On the left side of the operation, write the current datetime value: now.
  • On the right side of the operation:
    • seconds or s.
    • minutes or m.
    • hours or h.
    • days or d.
    • weeks or w.
    • If you enter the number 1, you can write second, minute, etc.
    • Don't enter a space between now and the operation symbol. Optionally you can add (or not) a space around the number value.

Let's see some examples of the date values that you can enter:

  • now-1 day
  • now+ 8days
  • now+ 1 h
  • now-4hours
  • now- 3 weeks
  • now-1 weeks

View Timeline

In the Django admin, you can select a drip and then click on the VIEW TIMELINE button to view the mails expected to be sent with the corresponding receivers:

Timeline

Send drips

To send the created and enabled drips, run the command:

python manage.py send_drips

You can use cron to schedule the drips.

The cron scheduler

You may want to have an easy way to send drips periodically. It's possible to set a couple of parameters in your settings to do that. First activate the scheduler by adding the DRIP_SCHEDULE_SETTINGS dictionary:

# your settings file
DRIP_SCHEDULE_SETTINGS = {
   'DRIP_SCHEDULE': True,
}

After that, choose:

  • A day of the week: An integer value between 0-6, or a string: 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'. The name in the settings is DRIP_SCHEDULE_DAY_OF_WEEK (default is set to 0).
  • An hour: An integer value between 0-23. The name in the settings is DRIP_SCHEDULE_HOUR (default is set to 0).
  • A minute: An integer value between 0-59. The name in the settings is DRIP_SCHEDULE_MINUTE (default is set to 0).

With those values, a cron scheduler will execute the send_drips command every week in the specified day/hour/minute. The scheduler will use the timezone of your TIME_ZONE parameter in your settings (default is set to 'UTC'). For example, if you have:

DRIP_SCHEDULE_SETTINGS = {
   'DRIP_SCHEDULE': True,
   'DRIP_SCHEDULE_DAY_OF_WEEK': 'mon',
   'DRIP_SCHEDULE_HOUR': 13,
   'DRIP_SCHEDULE_MINUTE': 57,
}

Then every Monday at 13:57 the send_drips command will be executed. Last but not least, add this line at the end of your main urls.py file to start the scheduler:

# your main urls.py file
...
from drip.scheduler.cron_scheduler import cron_send_drips

...
cron_send_drips()

We recommend you to do it there because we know for sure that it's a file that is executed once at the beginning.

Some tips:

  • If you want to run the command every day in the week, hour, or minute, just set the corresponding parameter to '*'.
  • If you want to run the command more than a day in the week, just set the DRIP_SCHEDULE_DAY_OF_WEEK to more than one value. For example, if you set that to 'mon-fri' the command will be executed from Monday to Friday.

Celery integration

IMPORTANT: We use Celery 5.2.2 that supports Django 1.11 LTS or newer versions.

If you need to use celery it can be configured in the same way you just need to add the following key SCHEDULER setted as "CELERY":

DRIP_SCHEDULE_SETTINGS = {
   'DRIP_SCHEDULE': True,
   'DRIP_SCHEDULE_DAY_OF_WEEK': 'mon',
   'DRIP_SCHEDULE_HOUR': 13,
   'DRIP_SCHEDULE_MINUTE': 57,
   'SCHEDULER': "CELERY",
}

The default value of this key is "CRON", if you enable DRIP_SCHEDULE it will work with a Cron by default.

In order to make this happen, the project's celery.py setup shall invoke the autodiscoverttasks function. This task is scheduled with a simple Celery beat configuration.

django-drip-campaigns's People

Contributors

akoumjian avatar brad avatar brunomichetti avatar bryanhelmig avatar budlight avatar dependabot[bot] avatar guanlisheng avatar guilleijo avatar hwkns avatar jacebrowning avatar jarcoal avatar jsocol avatar jtrain avatar juan-ignacio-sanchez avatar jumcorredorro avatar kmtracey avatar markrofail avatar nsantos16 avatar roycehaynes avatar rririanto avatar sayar avatar syphar avatar timgates42 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-drip-campaigns's Issues

Integrate Code Climate

Configure code climate into the project
it should track

  • Maintainability
  • Code coverage

Available functions documentation

  • We need to read the code and create a documentation of the available functions in the drip creation, in Field value.
  • For example, if we use dates, we can enter: now - 7 days and that works. We need to document the possibilities for the user and add that to the README and the docs

Error in related fields of user in drip creation

  • In the admin, in the creation drip form, the fields related to the user but in a different model, are shown in a wrong way:

For example, is_superuser field in the logentry model, is shown as logentry__is_superuser. But if you select that field, then you won't be able to create a drip because the correct way is: logentry__user__is_superuser. The __user__ substring is missing

Campaigns

A campaign takes several emails and users entering in and out of it.
This is possible right now trough multiple drips but is not very intuitive.

We need to track how many users are in the campaign and how many users exited that campaign.

Integrate CI/CD

  • run flake8
  • run pylint
  • run tests (Tests should run on python 3.5+ and django 2.2+)
  • generate and update docs

HTML Emails

Hello,
Firstly, thanks for maintaining this package, it is very useful.

Secondly, is it possible to send HTML emails?
Also, how about a WYSIWYG editor like CKEditor for composing a well formatted email.

Any ideas on how to achieve this?

NoReverseMatch when viewing drip timeline if user uses UUID

My users have a UUID, and django-drip-campaigns doesn't account for that possibility in the URL reverse.

Traceback (most recent call last) File "/usr/local/lib/python3.8/site-packages/django/contrib/staticfiles/handlers.py", line 76, in __call__ return self.application(environ, start_response) File "/usr/local/lib/python3.8/site-packages/django/core/handlers/wsgi.py", line 133, in __call__ response = self.get_response(request) File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 128, in get_response response = self._middleware_chain(request) File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 49, in inner response = response_for_exception(request, exc) File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 103, in response_for_exception response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info()) File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 138, in handle_uncaught_exception return debug.technical_500_response(request, *exc_info) File "/usr/local/lib/python3.8/site-packages/django_extensions/management/technical_response.py", line 40, in null_technical_500_response raise exc_value.with_traceback(tb) File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner response = get_response(request) File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/usr/local/lib/python3.8/contextlib.py", line 75, in inner return func(*args, **kwds) File "/usr/local/lib/python3.8/site-packages/django/utils/decorators.py", line 130, in _wrapped_view response = view_func(request, *args, **kwargs) File "/usr/local/lib/python3.8/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func response = view_func(request, *args, **kwargs) File "/usr/local/lib/python3.8/site-packages/django/contrib/admin/sites.py", line 233, in inner return view(request, *args, **kwargs) File "/usr/local/lib/python3.8/site-packages/drip/admin.py", line 65, in timeline return render(request, 'drip/timeline.html', locals()) File "/usr/local/lib/python3.8/site-packages/django/shortcuts.py", line 19, in render content = loader.render_to_string(template_name, context, request, using=using) File "/usr/local/lib/python3.8/site-packages/django/template/loader.py", line 62, in render_to_string return template.render(context, request) File "/usr/local/lib/python3.8/site-packages/django/template/backends/django.py", line 61, in render return self.template.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 170, in render return self._render(context) File "/usr/local/lib/python3.8/site-packages/django/test/utils.py", line 96, in instrumented_test_render return self.nodelist.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 938, in render bit = node.render_annotated(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/loader_tags.py", line 150, in render return compiled_parent._render(context) File "/usr/local/lib/python3.8/site-packages/django/test/utils.py", line 96, in instrumented_test_render return self.nodelist.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 938, in render bit = node.render_annotated(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/loader_tags.py", line 150, in render return compiled_parent._render(context) File "/usr/local/lib/python3.8/site-packages/django/test/utils.py", line 96, in instrumented_test_render return self.nodelist.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 938, in render bit = node.render_annotated(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/loader_tags.py", line 62, in render result = block.nodelist.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 938, in render bit = node.render_annotated(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/defaulttags.py", line 211, in render nodelist.append(node.render_annotated(context)) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/defaulttags.py", line 312, in render return nodelist.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 938, in render bit = node.render_annotated(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/defaulttags.py", line 211, in render nodelist.append(node.render_annotated(context)) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/defaulttags.py", line 312, in render return nodelist.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 938, in render bit = node.render_annotated(context) File "/usr/local/lib/python3.8/site-packages/django/template/base.py", line 905, in render_annotated return self.render(context) File "/usr/local/lib/python3.8/site-packages/django/template/defaulttags.py", line 446, in render url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app) File "/usr/local/lib/python3.8/site-packages/django/urls/base.py", line 87, in reverse return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs)) File "/usr/local/lib/python3.8/site-packages/django/urls/resolvers.py", line 685, in _reverse_with_prefix raise NoReverseMatch(msg) django.urls.exceptions.NoReverseMatch: Reverse for 'view_drip_email' with arguments '(1, 4, 7, UUID('7ab86abc-7856-4f91-be1b-8bd6147b836c'))' not found. 1 pattern(s) tried: ['admin/drip/drip/(?P<drip_id>[0-9]+)/timeline/(?P<into_past>[0-9]+)/(?P<into_future>[0-9]+)/(?P<user_id>[0-9]+)/$']

Celery

RIght now drips are handled trough a cron job.
Integrating celery into it would be a good idea.
Since we want to support the current users, have the option to use either to send the drips

Warning when running tests

This warning is raised while running tests:

RuntimeWarning: DateTimeField User.date_joined received a naive datetime (...) while time zone support is active.

It is not a problem, just a warning in the tests

List available user's fields in admin

  • In the drip creation, we can choose any field of the user model to add a query set rule.
  • We should try to list those available fields in the admin to improve the query set rules creation.

Inconsistent String Formatting Syntax

We need to review the code and apply changes to maintain a consistent string formatting syntax.
We need to be careful to use a formatting syntax that is compatible with python 3.5 and further.

Generate and host documentation

Generate automated documentation for all the classes and functions in the code
Remove links to old documentation and rehost it

Review docstrings

Review and change docstrings of the classes to improve the autogenerated doc with sphinx.

Publish a new version every time something merges to master

in Travis CI.

Tasks to be done:

Every time something merges to master with the pattern [fix] create a minor version (Bump third digit)
Every time something merges to master with the pattern [feature] create a new version (Bump second digit)
Every time something merges to master with the pattern [breaking-change] create a new major version (Bump first digit)
Every time something merges to master with the pattern [docs] rebuild just the docs and publish them

Build the new changes
Build the docs
Publish the docs
Publish the changes to pypy

Update contributing.md to ask for those specific tags on PR titles.

Plans / Goals behind this project?

I am looking for an email drip system for my django project and see all the forks off the zapier project.

This looks like a pretty recent / updated effort, and I wanted to check in on the reasoning for creating it and plans.

For example, are you looking to just have it be updated and work on the newer version of django? Do you hope to keep adding features and have it become a more full-fledged product?

Ultimately, I'm looking for something that can do email flows like welcome / product tours. But also integrate with a web analytics data pipeline like what comes out of Mixpanel.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.