Open Source software (OSS) is a key initiative for developers at Rootstrap to stay sharp and engage with the developer community. In this blog post, I'm going to talk about a particular project named Django Drip Campaigns and the process of publishing it as a PyPI package.
Pypi (Python Package Index) is a repository of software for the Python programming language that helps us find and install software developed by the Python community. One of our contributions to the OSS community is to publish and maintain the mentioned project and I want to share that experience in this article.
The Django Drip Campaigns Project
Django Drip Campaigns Project is an open-source app to create drip campaigns for email using Django’s admin interface and a User queryset. This means that using this app, you are able to easily schedule and send emails to the registered users, creating querysets in a friendly interface.
For example, you can very quickly configure your app to send a periodic email to the users that haven't logged in in a day or more, send a custom email to the users that registered more than a week ago, etc. To do this, you only need to define one or more querysets in the admin page. If you want to install it and work with it, you can follow the simple steps documented in the github page of Django Drip Campaigns.
We didn't create this project, we just made a fork of the one written by Zapier that is no longer maintained. After this, we worked on fixing some bugs, doing a couple of enhancements, creating more documentation, and publishing the app as a PyPI package.
Since it's published, any programmer that wants to use this project in his Django app is able to quickly download it with a simple command described in the mentioned docs.
Features
After a successful installation and configuration, you are able to easily create drip campaigns for email using the admin interface of Django. If you don't have an admin user, create one with the Django createsuperuser command. After this, login into Django's admin page and click on Manage Them. There you are able to:
- View created and sent drips.
- Create a new drip.
- Select and delete drips.
Creating a New Drip
Click on the [.c-inline-code]ADD DRIP +[.c-inline-code] button to create a new drip. You will see a page like this:
In the next section, I will talk about the queryset rules that you can build in an easy way to create very useful drips.
Queryset Rules
When you create a drip, you need at least one queryset so the users in it will be the ones that receive the defined email. In the [.c-inline-code]METHOD TYPE[.c-inline-code] menu you are able to choose to filter or to exclude the users in the defined queryset.
On the other hand, when you click in the [.c-inline-code]FIELD NAME OF USER[.c-inline-code] input, you will see the fields of your User model and the fields of your user model in the models related to it:
In the previous image, for example, [.c-inline-code]last_login[.c-inline-code] is the field in the User model, and [.c-inline-code]groups__user__id[.c-inline-code] is the user id from the Groups model that is related to it. So you can enter the name of the field or select it from the list that you see when you click on the input.
After the selection of the field name, you have to choose the type of lookup that you want to do over the field. The possibilities are [.c-inline-code]exactly[.c-inline-code], [.c-inline-code]exactly (case insensitive)[.c-inline-code], [.c-inline-code]contains[.c-inline-code], [.c-inline-code]contains (case insensitive)[.c-inline-code], [.c-inline-code]greater than[.c-inline-code], [.c-inline-code]greater than or equal to[.c-inline-code], [.c-inline-code]less than[.c-inline-code], etc. This lookup type will be done over the user field and the [.c-inline-code]FIELD VALUE[.c-inline-code] that you enter.
The [.c-inline-code]FIELD VALUE[.c-inline-code] input can be a string, a number, or a regular expression. The correctness of the queryset rule will depend on the type of the user field, the lookup type, and the field value.
When you enter a user field that has a date type, Django Drip Campaigns allows you to enter a date value in natural language combining the current time and some operation with seconds, hours, days, etc. For example, if you have selected the field [.c-inline-code]last_login[.c-inline-code] 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:
[.c-inline-code]now-1 week
[.c-inline-code]
or
[.c-inline-code]now- 1 w[.c-inline-code]
Possible operations and values:
- Add ([.c-inline-code]+[.c-inline-code]) or subtract ([.c-inline-code]-[.c-inline-code]) dates.
- On the left side of the operation, write the current DateTime value: [.c-inline-code]now[.c-inline-code].
- On the right side of the operation:
- [.c-inline-code]seconds[.c-inline-code] or [.c-inline-code]s[.c-inline-code].
- [.c-inline-code]minutes[.c-inline-code] or [.c-inline-code]m[.c-inline-code].
- [.c-inline-code]hours[.c-inline-code] or [.c-inline-code]h[.c-inline-code].
- [.c-inline-code]days[.c-inline-code] or [.c-inline-code]d[.c-inline-code].
- [.c-inline-code]weeks[.c-inline-code] or [.c-inline-code]w[.c-inline-code].
- If you enter the number [.c-inline-code]1[.c-inline-code], you can write [.c-inline-code]second[.c-inline-code], [.c-inline-code]minute[.c-inline-code], etc.
- Don't enter a space between [.c-inline-code]now[.c-inline-code] 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:
- [.c-inline-code]now-1 day[.c-inline-code]
- [.c-inline-code]now+ 8days[.c-inline-code]
- [.c-inline-code]now+ 1 h[.c-inline-code]
- [.c-inline-code]now-4hours[.c-inline-code]
- [.c-inline-code]now- 3 weeks[.c-inline-code]
- [.c-inline-code]now-1 weeks[.c-inline-code]
To finalize this subsection, I would like to show you two examples to clarify the queryset rules creation:
- Send a recordatory email to the users that haven't logged in for a week or more:
- method type: filter
- user field: last_login
- lookup type: less than or equal to
- field value: now- 1 week
- Send an email to the users that have a Gmail account, excluding the ones that registered yesterday:
- Rule 1:
- method type: [.c-inline-code]filter[.c-inline-code]
- user field: [.c-inline-code]email[.c-inline-code]
- lookup type: [.c-inline-code]contains[.c-inline-code]
- field value: [.c-inline-code]gmail[.c-inline-code]
- Rule 2:
- method type: [.c-inline-code]exclude[.c-inline-code]
- user field: [.c-inline-code]date_joined[.c-inline-code]
- lookup type: [.c-inline-code]exactly[.c-inline-code]
- field value: [.c-inline-code]now- 1 d[.c-inline-code]
As you can see, the queryset rules creation is very powerful and for each drip, you can add as many as you want.
View the timeline of a Drip
In the django admin, you can select a drip and then click on the [.c-inline-code]VIEW TIMELINE[.c-inline-code] button to view the emails expected to be sent with the corresponding receivers:
Message Class
By default, Django Drip creates and sends messages that are instances of Django’s [.c-inline-code]EmailMultiAlternatives[.c-inline-code] class. If you want to customize in any way the message that is created and sent, you can do that by creating a subclass of [.c-inline-code]EmailMessage[.c-inline-code] and overriding any method that you want to behave differently. For example:
CODE: https://gist.github.com/brunomichetti/383deae6dc8b53d2d5b7c17634910bfe.js
[.c-inline-code][.c-inline-code]
In that example, [.c-inline-code]PlainDripEmail[.c-inline-code] overrides the message property of the base [.c-inline-code]DripMessage[.c-inline-code] class to create a simple [.c-inline-code]EmailMessage[.c-inline-code] instance instead of an [.c-inline-code]EmailMultiAlternatives[.c-inline-code] instance.
In order to be able to specify that your custom message class should be used for a drip, you need to configure it in the [.c-inline-code]DRIP_MESSAGE_CLASSES[.c-inline-code] setting:
CODE: https://gist.github.com/brunomichetti/101723c4bf6188b402af96f3776c3feb.js
[.c-inline-code]
[.c-inline-code]
This will allow you to choose in the admin, for each drip, whether the [.c-inline-code]default[.c-inline-code] ([.c-inline-code]DripMessage[.c-inline-code]) or [.c-inline-code]plain[.c-inline-code] message class should be used for generating and sending the messages to users.
Send Drips
To send the created and enabled drips, run the command:
[.c-inline-code]python manage.py send_drips[.c-inline-code]
You can use cron to schedule the drips.
What we have been doing in the project
Publish a PyPi Package
Despite Django Drip Campaigns is still a work in progress, we already published the package in PyPI. It's nothing fancy, but it's a must to know a few things before starting, so in this section, I'll be talking about the main steps for publishing an app.
- Project structure: Your project needs a couple of files in a determined place to be published correctly. An appropriate structure could be:
- AUTHORS: Specify the author's information of your project in this file.
- LICENSE: You have to specify the license of the project. That is a document that provides legally binding guidelines for the use and distribution of software. In OSS the most known are BSD and MIT. We used an MIT license in this case.
- MANIFEST: Here you can specify the files that aren't programming files but you want to include anyway in the build of your project. For example the author's file, the docs, etc.
- README.md: A file where you introduce and explain your project. It's very important because otherwise, you can have a very good and useful project but if new people see it and don't understand what it does nor how it works, then it won't be used.
- setup.py: This is a fundamental file. It contains a global setup function that is in charge of the building and configuration of your project at the moment of installation.
- docs folder: Here is where the files of the documentation are.
- Install [.c-inline-code]setuptools[.c-inline-code] and [.c-inline-code]wheel[.c-inline-code]. They are used to generate the distribution package: a group of files needed to upload your app.
[.c-inline-code]python3 -m pip install --user --upgrade setuptools wheel[.c-inline-code]
2. Generate the distribution package. The next command will create all the necessary files under a [.c-inline-code]dist/[.c-inline-code] folder:
[.c-inline-code]python3 setup.py sdist bdist_wheel[.c-inline-code]
3. Install twine, that a allows you to upload your app as a package:
[.c-inline-code]python3 -m pip install --user --upgrade twine[.c-inline-code]
4. Once installed, run twine to upload the archives under dist/. This will ask for a user and a password, that are the ones you used on the registration at PyPI:
[.c-inline-code]python3 -m twine upload --repository pypi dist/*[.c-inline-code]
If everything went well, you have published your project as a PyPI package. Now you anyone is able to download it with:
[.c-inline-code]pip3 install <project-name>[.c-inline-code]
Summary
In this blog post I talked about the Django Drip Campaigns project, its main features and what we did with it at Rootstrap. We love to make contributions to OSS because it's a wonderful way of learning, of sharing knowledge, and creating useful programs to be used by other programmers.
Besides I talked about the experience and how to publish a project as a pypi package so anyone who wants to give it a try can download it with a simple command.
OSS gives a great opportunity not only to see how some other group of people solves a problem addressed by the program, but also to purpose and contribute with fixes and improvements. Finally, I hope you have enjoyed this blog and feel motivated to use and work with Django Drip Campaigns and to contribute with OSS.