django-mail-analytics 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. django_mail_analytics-1.0.0/LICENSE.txt +21 -0
  2. django_mail_analytics-1.0.0/MANIFEST.in +4 -0
  3. django_mail_analytics-1.0.0/PKG-INFO +195 -0
  4. django_mail_analytics-1.0.0/README.md +146 -0
  5. django_mail_analytics-1.0.0/django_mail_analytics/__init__.py +17 -0
  6. django_mail_analytics-1.0.0/django_mail_analytics/admin.py +158 -0
  7. django_mail_analytics-1.0.0/django_mail_analytics/apps.py +14 -0
  8. django_mail_analytics-1.0.0/django_mail_analytics/mail.py +135 -0
  9. django_mail_analytics-1.0.0/django_mail_analytics/metadata.py +10 -0
  10. django_mail_analytics-1.0.0/django_mail_analytics/migrations/0001_initial.py +44 -0
  11. django_mail_analytics-1.0.0/django_mail_analytics/migrations/__init__.py +0 -0
  12. django_mail_analytics-1.0.0/django_mail_analytics/models.py +53 -0
  13. django_mail_analytics-1.0.0/django_mail_analytics/templates/django_mail_analytics/mail_list.html +52 -0
  14. django_mail_analytics-1.0.0/django_mail_analytics/tests.py +3 -0
  15. django_mail_analytics-1.0.0/django_mail_analytics/urls.py +13 -0
  16. django_mail_analytics-1.0.0/django_mail_analytics/views.py +67 -0
  17. django_mail_analytics-1.0.0/django_mail_analytics.egg-info/PKG-INFO +195 -0
  18. django_mail_analytics-1.0.0/django_mail_analytics.egg-info/SOURCES.txt +23 -0
  19. django_mail_analytics-1.0.0/django_mail_analytics.egg-info/dependency_links.txt +1 -0
  20. django_mail_analytics-1.0.0/django_mail_analytics.egg-info/top_level.txt +1 -0
  21. django_mail_analytics-1.0.0/pyproject.toml +89 -0
  22. django_mail_analytics-1.0.0/setup.cfg +4 -0
  23. django_mail_analytics-1.0.0/setup.py +4 -0
  24. django_mail_analytics-1.0.0/tests/test_mail.py +173 -0
  25. django_mail_analytics-1.0.0/tests/test_metadata.py +34 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-present Loic Quertenmont
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include LICENSE.txt
2
+ include README.md
3
+ recursive-include django_mail_analytics *
4
+ recursive-exclude * *.pyc __pycache__ .DS_Store
@@ -0,0 +1,195 @@
1
+ Metadata-Version: 2.1
2
+ Name: django-mail-analytics
3
+ Version: 1.0.0
4
+ Summary: django package adding email tracking features. Both email opening and link clicking are tracked.
5
+ Author-email: Loic Quertenmont <loic@deeperanalytics.be>
6
+ Maintainer-email: Loic Quertenmont <loic@deeperanalytics.be>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2024-present Loic Quertenmont
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Project-URL: Homepage, https://github.com/quertenmont/django-mail-analytics
30
+ Project-URL: Download, https://github.com/quertenmont/django-mail-analytics/releases
31
+ Project-URL: Documentation, https://github.com/quertenmont/django-mail-analytics#readme
32
+ Project-URL: Issues, https://github.com/quertenmont/django-mail-analytics/issues
33
+ Project-URL: Funding, https://github.com/sponsors/quertenmont/
34
+ Project-URL: Twitter, https://twitter.com/LoicQuertenmont
35
+ Keywords: django,mail,analytics,tracking,tracker,python
36
+ Classifier: Development Status :: 5 - Production/Stable
37
+ Classifier: Environment :: Web Environment
38
+ Classifier: Framework :: Django
39
+ Classifier: Framework :: Django :: 5.0
40
+ Classifier: Intended Audience :: Developers
41
+ Classifier: License :: OSI Approved :: MIT License
42
+ Classifier: Natural Language :: English
43
+ Classifier: Operating System :: OS Independent
44
+ Classifier: Programming Language :: Python :: 3.11
45
+ Classifier: Programming Language :: Python :: 3.12
46
+ Classifier: Topic :: Software Development :: Build Tools
47
+ Description-Content-Type: text/markdown
48
+ License-File: LICENSE.txt
49
+
50
+ [![](https://img.shields.io/pypi/pyversions/django-mail-analytics.svg?color=3776AB&logo=python&logoColor=white)](https://www.python.org/)
51
+ [![](https://img.shields.io/pypi/djversions/django-mail-analytics?color=0C4B33&logo=django&logoColor=white&label=django)](https://www.djangoproject.com/)
52
+
53
+ [![](https://img.shields.io/pypi/v/django-mail-analytics.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-mail-analytics/)
54
+ [![](https://static.pepy.tech/badge/django-mail-analytics/month)](https://pepy.tech/project/django-mail-analytics)
55
+ [![](https://img.shields.io/github/stars/quertenmont/django-mail-analytics?logo=github&style=flat)](https://github.com/quertenmont/django-mail-analytics/stargazers)
56
+ [![](https://img.shields.io/pypi/l/django-mail-analytics.svg?color=blue)](https://github.com/quertenmont/django-mail-analytics/blob/main/LICENSE.txt)
57
+
58
+ [![](https://results.pre-commit.ci/badge/github/quertenmont/django-mail-analytics/main.svg)](https://results.pre-commit.ci/latest/github/quertenmont/django-mail-analytics/main)
59
+ [![](https://img.shields.io/github/actions/workflow/status/quertenmont/django-mail-analytics/test-package.yml?branch=main&label=build&logo=github)](https://github.com/quertenmont/django-mail-analytics)
60
+ [![](https://img.shields.io/codecov/c/gh/quertenmont/django-mail-analytics?logo=codecov)](https://codecov.io/gh/quertenmont/django-mail-analytics)
61
+ [![](https://img.shields.io/codacy/grade/194566618f424a819ce43450ea0af081?logo=codacy)](https://www.codacy.com/app/quertenmont/django-mail-analytics)
62
+ [![](https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&logoColor=black)](https://github.com/psf/black)
63
+ [![](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
64
+
65
+
66
+
67
+ # django-mail-analytics
68
+
69
+ Simple python module that add email tracking features for both mail opening (via a hidden pixel) and for link clicks (via a redirecting proxy)
70
+ - This works only for email with an html body
71
+ - The changes to your code is minimal as we monkey patch the standard django.core.mail.EmailMessage object
72
+ - Three new models are added to save mail sent, mail recipients and mail recipient's action such as opening and link clicking
73
+
74
+ ---
75
+
76
+ ## Installation
77
+ - Run `pip install django-mail-analytics`
78
+ - Add `django_mail_analytics` to your installed INSTALLED_APPS
79
+ - Add `re_path(r"^m/", include("django_mail_analytics.urls"))` to your urls
80
+ - Add this to your settings:
81
+ ```
82
+ MAIL_ANALYTICS = {
83
+ "DOMAIN": "localhost", #add the domain of your app to be used in the pixel/proxy url of your mail sent
84
+ "SCHEME": "http", #http or https
85
+ # "SALT": "my-salt", #comment to use global SECRET_KEY instead
86
+ "LENGTH": 6, # minimal length of encoded id
87
+ }
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Usage
93
+
94
+ All email sent with an html body will be modified on the fly to incorporate a pixel tracking and to rewrite link urls:
95
+
96
+ ```
97
+ from django.core.mail import send_mail
98
+
99
+ send_mail(
100
+ "Subject here",
101
+ "Here is the message.",
102
+ "from@example.com",
103
+ ["to@example.com"],
104
+ fail_silently=False,
105
+ html_message="""
106
+ <body>
107
+ <a href="https://google.com">link</a>
108
+ </body>
109
+ """,
110
+ )
111
+ ```
112
+ The above code will actually send an html body email that is rewritten like this:
113
+ ```
114
+ <body>
115
+ <a href="http://localhost/m/p?q=OJe2wr&u=https%3A%2F%2Fgoogle.com">link</a>
116
+ <img src="http://localhost/m/i?q=OJe2wr" height="0px" width="0px"/>
117
+ </body>
118
+ ```
119
+ There is no perciple change for the user that receive the email.
120
+ But as soon as it opens (or reopens) the email, we create a MailRecipientAction with an empty action.
121
+ If the user clicks on the link we would create another MailRecipientAction with an action equal to the link url: "https://google.com" in this example.
122
+
123
+
124
+ Every email send trigger the creation of a Mail object.
125
+ In addition, a MailRecipient object is also created for every recipients of the email.
126
+
127
+ Note, that multiple emails sharing the same "key" and sending "date" are merged together into a single Mail Object.
128
+ So no need to worry about sending mass emails (they will only create one single Mail object).
129
+ The email "key" is either the email subject OR the first recipient with a "@DMA" email address. This recipient is ignored from the sending list, it's just an easy way to track mails of a given category.
130
+
131
+ So typical usage is like this:
132
+ ```
133
+ send_mail(
134
+ subject="subject",
135
+ message="message",
136
+ html_message="html_message,
137
+ from_email=None,
138
+ recipient_list=[user.email, f"EMAIL_KEY@DMA"],
139
+ )
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Admin
145
+
146
+ There is a django admin for the three added models that can easilly be used to monitor your email success rate and typical use behaviour.
147
+
148
+ ---
149
+
150
+ ## Remarks
151
+
152
+ Email were not developped for tracking in the first place.
153
+ So adding a pixel tracking and link proxy is just a workaround to add tracking capabilities to email.
154
+ One of the limitation of this workaround is that sending the email to multiple recipients doesn't allow to distinguish who among the recipients is currently opening the email... just that someone did it.
155
+ Similarly if the email is forwarded to somebody else, we can only know that the email was opened a second time, but not that it's from a different people.
156
+
157
+ ---
158
+
159
+ ## Testing
160
+ ```bash
161
+ # clone repository
162
+ git clone https://github.com/quertenmont/django-mail-analytics.git && cd django_mail_analytics
163
+
164
+ # create virtualenv and activate it
165
+ python -m venv venv && . venv/bin/activate
166
+
167
+ # upgrade pip
168
+ python -m pip install --upgrade pip
169
+
170
+ # install requirements
171
+ pip install -r requirements.txt -r requirements-test.txt
172
+
173
+ # install pre-commit to run formatters and linters
174
+ pre-commit install --install-hooks
175
+
176
+ # run tests
177
+ tox
178
+ # or
179
+ python runtests.py
180
+ # or
181
+ python -m django test --settings "tests.settings"
182
+ ```
183
+ ---
184
+
185
+ ## License
186
+ Released under [MIT License](LICENSE.txt).
187
+
188
+ ---
189
+
190
+ ## Supporting
191
+
192
+ - :star: Star this project on [GitHub](https://github.com/quertenmont/django-mail-analytics)
193
+ - :octocat: Follow me on [GitHub](https://github.com/quertenmont)
194
+ - :blue_heart: Follow me on [Twitter](https://twitter.com/LoicQuertenmont)
195
+ - :moneybag: Sponsor me on [Github](https://github.com/sponsors/quertenmont)
@@ -0,0 +1,146 @@
1
+ [![](https://img.shields.io/pypi/pyversions/django-mail-analytics.svg?color=3776AB&logo=python&logoColor=white)](https://www.python.org/)
2
+ [![](https://img.shields.io/pypi/djversions/django-mail-analytics?color=0C4B33&logo=django&logoColor=white&label=django)](https://www.djangoproject.com/)
3
+
4
+ [![](https://img.shields.io/pypi/v/django-mail-analytics.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-mail-analytics/)
5
+ [![](https://static.pepy.tech/badge/django-mail-analytics/month)](https://pepy.tech/project/django-mail-analytics)
6
+ [![](https://img.shields.io/github/stars/quertenmont/django-mail-analytics?logo=github&style=flat)](https://github.com/quertenmont/django-mail-analytics/stargazers)
7
+ [![](https://img.shields.io/pypi/l/django-mail-analytics.svg?color=blue)](https://github.com/quertenmont/django-mail-analytics/blob/main/LICENSE.txt)
8
+
9
+ [![](https://results.pre-commit.ci/badge/github/quertenmont/django-mail-analytics/main.svg)](https://results.pre-commit.ci/latest/github/quertenmont/django-mail-analytics/main)
10
+ [![](https://img.shields.io/github/actions/workflow/status/quertenmont/django-mail-analytics/test-package.yml?branch=main&label=build&logo=github)](https://github.com/quertenmont/django-mail-analytics)
11
+ [![](https://img.shields.io/codecov/c/gh/quertenmont/django-mail-analytics?logo=codecov)](https://codecov.io/gh/quertenmont/django-mail-analytics)
12
+ [![](https://img.shields.io/codacy/grade/194566618f424a819ce43450ea0af081?logo=codacy)](https://www.codacy.com/app/quertenmont/django-mail-analytics)
13
+ [![](https://img.shields.io/badge/code%20style-black-000000.svg?logo=python&logoColor=black)](https://github.com/psf/black)
14
+ [![](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
15
+
16
+
17
+
18
+ # django-mail-analytics
19
+
20
+ Simple python module that add email tracking features for both mail opening (via a hidden pixel) and for link clicks (via a redirecting proxy)
21
+ - This works only for email with an html body
22
+ - The changes to your code is minimal as we monkey patch the standard django.core.mail.EmailMessage object
23
+ - Three new models are added to save mail sent, mail recipients and mail recipient's action such as opening and link clicking
24
+
25
+ ---
26
+
27
+ ## Installation
28
+ - Run `pip install django-mail-analytics`
29
+ - Add `django_mail_analytics` to your installed INSTALLED_APPS
30
+ - Add `re_path(r"^m/", include("django_mail_analytics.urls"))` to your urls
31
+ - Add this to your settings:
32
+ ```
33
+ MAIL_ANALYTICS = {
34
+ "DOMAIN": "localhost", #add the domain of your app to be used in the pixel/proxy url of your mail sent
35
+ "SCHEME": "http", #http or https
36
+ # "SALT": "my-salt", #comment to use global SECRET_KEY instead
37
+ "LENGTH": 6, # minimal length of encoded id
38
+ }
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Usage
44
+
45
+ All email sent with an html body will be modified on the fly to incorporate a pixel tracking and to rewrite link urls:
46
+
47
+ ```
48
+ from django.core.mail import send_mail
49
+
50
+ send_mail(
51
+ "Subject here",
52
+ "Here is the message.",
53
+ "from@example.com",
54
+ ["to@example.com"],
55
+ fail_silently=False,
56
+ html_message="""
57
+ <body>
58
+ <a href="https://google.com">link</a>
59
+ </body>
60
+ """,
61
+ )
62
+ ```
63
+ The above code will actually send an html body email that is rewritten like this:
64
+ ```
65
+ <body>
66
+ <a href="http://localhost/m/p?q=OJe2wr&u=https%3A%2F%2Fgoogle.com">link</a>
67
+ <img src="http://localhost/m/i?q=OJe2wr" height="0px" width="0px"/>
68
+ </body>
69
+ ```
70
+ There is no perciple change for the user that receive the email.
71
+ But as soon as it opens (or reopens) the email, we create a MailRecipientAction with an empty action.
72
+ If the user clicks on the link we would create another MailRecipientAction with an action equal to the link url: "https://google.com" in this example.
73
+
74
+
75
+ Every email send trigger the creation of a Mail object.
76
+ In addition, a MailRecipient object is also created for every recipients of the email.
77
+
78
+ Note, that multiple emails sharing the same "key" and sending "date" are merged together into a single Mail Object.
79
+ So no need to worry about sending mass emails (they will only create one single Mail object).
80
+ The email "key" is either the email subject OR the first recipient with a "@DMA" email address. This recipient is ignored from the sending list, it's just an easy way to track mails of a given category.
81
+
82
+ So typical usage is like this:
83
+ ```
84
+ send_mail(
85
+ subject="subject",
86
+ message="message",
87
+ html_message="html_message,
88
+ from_email=None,
89
+ recipient_list=[user.email, f"EMAIL_KEY@DMA"],
90
+ )
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Admin
96
+
97
+ There is a django admin for the three added models that can easilly be used to monitor your email success rate and typical use behaviour.
98
+
99
+ ---
100
+
101
+ ## Remarks
102
+
103
+ Email were not developped for tracking in the first place.
104
+ So adding a pixel tracking and link proxy is just a workaround to add tracking capabilities to email.
105
+ One of the limitation of this workaround is that sending the email to multiple recipients doesn't allow to distinguish who among the recipients is currently opening the email... just that someone did it.
106
+ Similarly if the email is forwarded to somebody else, we can only know that the email was opened a second time, but not that it's from a different people.
107
+
108
+ ---
109
+
110
+ ## Testing
111
+ ```bash
112
+ # clone repository
113
+ git clone https://github.com/quertenmont/django-mail-analytics.git && cd django_mail_analytics
114
+
115
+ # create virtualenv and activate it
116
+ python -m venv venv && . venv/bin/activate
117
+
118
+ # upgrade pip
119
+ python -m pip install --upgrade pip
120
+
121
+ # install requirements
122
+ pip install -r requirements.txt -r requirements-test.txt
123
+
124
+ # install pre-commit to run formatters and linters
125
+ pre-commit install --install-hooks
126
+
127
+ # run tests
128
+ tox
129
+ # or
130
+ python runtests.py
131
+ # or
132
+ python -m django test --settings "tests.settings"
133
+ ```
134
+ ---
135
+
136
+ ## License
137
+ Released under [MIT License](LICENSE.txt).
138
+
139
+ ---
140
+
141
+ ## Supporting
142
+
143
+ - :star: Star this project on [GitHub](https://github.com/quertenmont/django-mail-analytics)
144
+ - :octocat: Follow me on [GitHub](https://github.com/quertenmont)
145
+ - :blue_heart: Follow me on [Twitter](https://twitter.com/LoicQuertenmont)
146
+ - :moneybag: Sponsor me on [Github](https://github.com/sponsors/quertenmont)
@@ -0,0 +1,17 @@
1
+ from django_mail_analytics.metadata import (
2
+ __author__,
3
+ __copyright__,
4
+ __description__,
5
+ __license__,
6
+ __title__,
7
+ __version__,
8
+ )
9
+
10
+ __all__ = [
11
+ "__author__",
12
+ "__copyright__",
13
+ "__description__",
14
+ "__license__",
15
+ "__title__",
16
+ "__version__",
17
+ ]
@@ -0,0 +1,158 @@
1
+ from django.contrib import admin
2
+ from django.db.models import Count, Q
3
+ from django.utils.safestring import mark_safe
4
+
5
+ from .models import Mail, MailRecipient, MailRecipientAction
6
+
7
+
8
+ class MailRecipientActionInline(admin.TabularInline):
9
+ model = MailRecipientAction
10
+ fields = ("pk", "created", "action")
11
+ readonly_fields = ("pk", "created", "action")
12
+ can_delete = False
13
+ extra = 0
14
+ max_num = 0
15
+ show_change_link = True
16
+
17
+
18
+ @admin.register(MailRecipientAction)
19
+ class MailRecipientActionAdmin(admin.ModelAdmin):
20
+ """Define admin model for MailRecipientAction model."""
21
+
22
+ list_display = ("id", "created", "action", "recipient_email", "recipient_mail_key")
23
+ readonly_fields = ("id", "created", "recipient", "action")
24
+
25
+
26
+ class MailRecipientInline(admin.TabularInline):
27
+ model = MailRecipient
28
+ fields = ("pk", "created", "email", "sent", "opened", "openings", "clicks")
29
+ readonly_fields = ("pk", "created", "email", "sent", "opened", "openings", "clicks")
30
+ can_delete = False
31
+ extra = 0
32
+ max_num = 0
33
+ show_change_link = True
34
+
35
+ def get_queryset(self, request):
36
+ opening = Q(actions__action="")
37
+ hasAction = Q(actions__isnull=False)
38
+ qs = super().get_queryset(request)
39
+ qs = qs.annotate(opened=Count("id", filter=hasAction & opening, distinct=True))
40
+ qs = qs.annotate(sent=Count("id", distinct=True))
41
+ qs = qs.annotate(openings=Count("actions", filter=opening))
42
+ qs = qs.annotate(clicks=Count("actions", filter=~opening))
43
+ return qs
44
+
45
+ @admin.display(description="Opened")
46
+ def opened(self, instance):
47
+ return instance.opened
48
+
49
+ @admin.display(description="Sent")
50
+ def sent(self, instance):
51
+ return instance.sent
52
+
53
+ @admin.display(description="Openings")
54
+ def openings(self, instance):
55
+ return instance.openings
56
+
57
+ @admin.display(description="Clicks")
58
+ def clicks(self, instance):
59
+ return instance.clicks
60
+
61
+
62
+ @admin.register(MailRecipient)
63
+ class MailRecipientAdmin(admin.ModelAdmin):
64
+ """Define admin model for MailRecipient model."""
65
+
66
+ def get_queryset(self, request):
67
+ opening = Q(actions__action="")
68
+ hasAction = Q(actions__isnull=False)
69
+
70
+ qs = super().get_queryset(request)
71
+ qs = qs.annotate(opened=Count("id", filter=hasAction & opening, distinct=True))
72
+ qs = qs.annotate(sent=Count("id", distinct=True))
73
+ qs = qs.annotate(openings=Count("actions", filter=opening))
74
+ qs = qs.annotate(clicks=Count("actions", filter=~opening))
75
+ return qs
76
+
77
+ @admin.display(description="Success Rate")
78
+ def rate(self, instance):
79
+ return f"{int(100*(instance.opened/instance.sent))}%"
80
+
81
+ @admin.display(description="Opened")
82
+ def opened(self, instance):
83
+ return instance.opened
84
+
85
+ @admin.display(description="Sent")
86
+ def sent(self, instance):
87
+ return instance.sent
88
+
89
+ @admin.display(description="Openings")
90
+ def openings(self, instance):
91
+ return instance.openings
92
+
93
+ @admin.display(description="Clicks")
94
+ def clicks(self, instance):
95
+ return instance.clicks
96
+
97
+ list_display = ("id", "created", "mail_key", "email", "rate", "opened", "sent", "openings", "clicks")
98
+ readonly_fields = (
99
+ "id",
100
+ "mail",
101
+ "recipient",
102
+ )
103
+ inlines = [
104
+ MailRecipientActionInline,
105
+ ]
106
+
107
+
108
+ @admin.register(Mail)
109
+ class MailAdmin(admin.ModelAdmin):
110
+ """Define admin model for Mail model."""
111
+
112
+ def get_queryset(self, request):
113
+ opening = Q(recipients__actions__action="")
114
+ hasAction = Q(recipients__actions__isnull=False)
115
+
116
+ qs = super().get_queryset(request)
117
+ qs = qs.annotate(opened=Count("recipients", filter=hasAction & opening, distinct=True))
118
+ qs = qs.annotate(sent=Count("recipients", distinct=True))
119
+ qs = qs.annotate(openings=Count("recipients__actions", filter=opening))
120
+ qs = qs.annotate(clicks=Count("recipients__actions", filter=~opening))
121
+ return qs
122
+
123
+ def body_html(self, obj):
124
+ return mark_safe(obj.body)
125
+
126
+ @admin.display(description="Success Rate")
127
+ def rate(self, instance):
128
+ return f"{int(100*(instance.opened/instance.sent))}%"
129
+
130
+ @admin.display(description="Opened")
131
+ def opened(self, instance):
132
+ return instance.opened
133
+
134
+ @admin.display(description="Sent")
135
+ def sent(self, instance):
136
+ return instance.sent
137
+
138
+ @admin.display(description="Openings")
139
+ def openings(self, instance):
140
+ return instance.openings
141
+
142
+ @admin.display(description="Clicks")
143
+ def clicks(self, instance):
144
+ return instance.clicks
145
+
146
+ exclude = ("body",)
147
+ readonly_fields = (
148
+ "id",
149
+ "key",
150
+ "sender",
151
+ "subject",
152
+ "body_html",
153
+ )
154
+
155
+ list_display = ("id", "created", "key", "subject", "rate", "opened", "sent", "openings", "clicks")
156
+ inlines = [
157
+ MailRecipientInline,
158
+ ]
@@ -0,0 +1,14 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DjangoMailAnalyticsConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "django_mail_analytics"
7
+
8
+ def ready(self):
9
+ # pylint: disable=import-outside-toplevel
10
+
11
+ # monky patch mail functions
12
+ from .mail import send
13
+
14
+ _ = send # just there to avoid autoremoval of the import