pretix-mailchimp 0.1.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.
- pretix_mailchimp-0.1.0/LICENSE +15 -0
- pretix_mailchimp-0.1.0/MANIFEST.in +5 -0
- pretix_mailchimp-0.1.0/PKG-INFO +146 -0
- pretix_mailchimp-0.1.0/README.rst +132 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/__init__.py +1 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/apps.py +36 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/defaults.py +41 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/forms.py +106 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/locale/de/LC_MESSAGES/django.mo +0 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/locale/de/LC_MESSAGES/django.po +12 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/locale/de_Informal/.gitkeep +0 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/locale/de_Informal/LC_MESSAGES/django.mo +0 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/locale/de_Informal/LC_MESSAGES/django.po +12 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/mailchimp.py +97 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/signals.py +127 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/static/pretix_mailchimp/.gitkeep +0 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/tasks.py +82 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/templates/.DS_Store +0 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/templates/pretix_mailchimp/.gitkeep +0 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/templates/pretix_mailchimp/event_settings.html +24 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/templates/pretix_mailchimp/organizer_settings.html +22 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/tests.py +74 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/urls.py +18 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/utils.py +105 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp/views.py +97 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp.egg-info/PKG-INFO +146 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp.egg-info/SOURCES.txt +33 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp.egg-info/dependency_links.txt +1 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp.egg-info/entry_points.txt +5 -0
- pretix_mailchimp-0.1.0/pretix_mailchimp.egg-info/top_level.txt +1 -0
- pretix_mailchimp-0.1.0/pyproject.toml +42 -0
- pretix_mailchimp-0.1.0/setup.cfg +49 -0
- pretix_mailchimp-0.1.0/setup.py +3 -0
- pretix_mailchimp-0.1.0/tests/test_main.py +3 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
Copyright 2026 Julian Hammer
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pretix-mailchimp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Mailchimp newsletter subscription with booking (opt-in)
|
|
5
|
+
Author-email: Julian Hammer <julian.hammer@betreiberverein.de>
|
|
6
|
+
Maintainer-email: Julian Hammer <julian.hammer@betreiberverein.de>
|
|
7
|
+
License: Apache
|
|
8
|
+
Project-URL: homepage, https://github.com/zam-haus/pretix-mailchimp
|
|
9
|
+
Project-URL: repository, https://github.com/zam-haus/pretix-mailchimp
|
|
10
|
+
Keywords: pretix
|
|
11
|
+
Description-Content-Type: text/x-rst
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
pretix Mailchimp Newsletter
|
|
16
|
+
===========================
|
|
17
|
+
|
|
18
|
+
This is a plugin for `pretix`_.
|
|
19
|
+
|
|
20
|
+
Mailchimp newsletter subscription with booking (opt-in).
|
|
21
|
+
|
|
22
|
+
Behavior
|
|
23
|
+
--------
|
|
24
|
+
|
|
25
|
+
* Activated on organizer level.
|
|
26
|
+
* Applies to all events of the organizer by default.
|
|
27
|
+
* Can only be disabled per event.
|
|
28
|
+
* Shows an optional checkbox in the booking contact form.
|
|
29
|
+
* Also shows an optional checkbox in the attendee/question form for every personalized order position, but only if the
|
|
30
|
+
event asks for email addresses per ticket.
|
|
31
|
+
* If no checkbox is selected, nothing is sent to Mailchimp.
|
|
32
|
+
* If selected, the contact is created or updated in the configured Mailchimp audience.
|
|
33
|
+
* The contact is tagged with the main pretix event, not the subevent/date.
|
|
34
|
+
* Existing contacts are not overwritten unnecessarily; the event tag is added.
|
|
35
|
+
* If booking contact and attendee use the same email address in one order, the plugin avoids duplicate Mailchimp calls.
|
|
36
|
+
|
|
37
|
+
Default public text::
|
|
38
|
+
|
|
39
|
+
ZAMgefasst, der Newsletter 1x pro Monat aus dem ZAM
|
|
40
|
+
|
|
41
|
+
Installation
|
|
42
|
+
------------
|
|
43
|
+
|
|
44
|
+
Inside your pretix virtualenv::
|
|
45
|
+
|
|
46
|
+
pip install -e .
|
|
47
|
+
|
|
48
|
+
Then restart pretix and enable the plugin on organizer level.
|
|
49
|
+
|
|
50
|
+
Configuration
|
|
51
|
+
-------------
|
|
52
|
+
|
|
53
|
+
Go to the plugin settings on organizer level and configure:
|
|
54
|
+
|
|
55
|
+
* Mailchimp API key
|
|
56
|
+
* Mailchimp Audience/List ID
|
|
57
|
+
* status for new contacts (``pending`` recommended for double opt-in)
|
|
58
|
+
* tag template
|
|
59
|
+
* public checkbox label, with translations
|
|
60
|
+
* public help text, with translations
|
|
61
|
+
|
|
62
|
+
Per event, go to event settings and use **Newsletter opt-in deaktivieren** if the opt-in should not be shown for that
|
|
63
|
+
event.
|
|
64
|
+
|
|
65
|
+
Opt-in behavior
|
|
66
|
+
---------------
|
|
67
|
+
|
|
68
|
+
The plugin stores and evaluates two independent opt-ins:
|
|
69
|
+
|
|
70
|
+
* booking contact opt-in: saved in ``order.meta_info["contact_form_data"]``
|
|
71
|
+
* attendee/position opt-in: saved in ``position.meta_info["question_form_data"]``
|
|
72
|
+
|
|
73
|
+
This matters for events with non-personalized products: If pretix does not ask for attendee name/email per position, the
|
|
74
|
+
booking contact can still explicitly subscribe.
|
|
75
|
+
|
|
76
|
+
Mailchimp tag template placeholders
|
|
77
|
+
-----------------------------------
|
|
78
|
+
|
|
79
|
+
The tag template can use:
|
|
80
|
+
|
|
81
|
+
* ``{organizer}`` - organizer slug
|
|
82
|
+
* ``{event_slug}`` - event slug
|
|
83
|
+
* ``{event}`` - event name
|
|
84
|
+
* ``{event_id}`` - internal event id
|
|
85
|
+
|
|
86
|
+
Example::
|
|
87
|
+
|
|
88
|
+
ZAM Veranstaltung: {event}
|
|
89
|
+
|
|
90
|
+
or more stable::
|
|
91
|
+
|
|
92
|
+
pretix:{organizer}:{event_slug}
|
|
93
|
+
|
|
94
|
+
Privacy note
|
|
95
|
+
------------
|
|
96
|
+
|
|
97
|
+
This plugin only transfers a contact after explicit opt-in. For new Mailchimp contacts, ``pending`` is the recommended
|
|
98
|
+
default so Mailchimp sends a confirmation email. Existing unsubscribed contacts are not forcefully resubscribed by
|
|
99
|
+
default; they only receive the event tag.
|
|
100
|
+
|
|
101
|
+
Development setup
|
|
102
|
+
-----------------
|
|
103
|
+
|
|
104
|
+
1. Make sure that you have a working `pretix development setup`_.
|
|
105
|
+
|
|
106
|
+
2. Clone this repository.
|
|
107
|
+
|
|
108
|
+
3. Activate the virtual environment you use for pretix development.
|
|
109
|
+
|
|
110
|
+
4. Execute ``python setup.py develop`` within this directory to register this application with pretix's plugin registry.
|
|
111
|
+
|
|
112
|
+
5. Execute ``make`` within this directory to compile translations.
|
|
113
|
+
|
|
114
|
+
6. Restart your local pretix server. You can now use the plugin from this repository for your events by enabling it in
|
|
115
|
+
the 'plugins' tab in the settings.
|
|
116
|
+
|
|
117
|
+
This plugin has CI set up to enforce a few code style rules. To check locally, you need these packages installed::
|
|
118
|
+
|
|
119
|
+
pip install flake8 isort black
|
|
120
|
+
|
|
121
|
+
To check your plugin for rule violations, run::
|
|
122
|
+
|
|
123
|
+
black --check .
|
|
124
|
+
isort -c .
|
|
125
|
+
flake8 .
|
|
126
|
+
|
|
127
|
+
You can auto-fix some of these issues by running::
|
|
128
|
+
|
|
129
|
+
isort .
|
|
130
|
+
black .
|
|
131
|
+
|
|
132
|
+
To automatically check for these issues before you commit, you can run ``.install-hooks``.
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
License
|
|
136
|
+
-------
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
Copyright 2026 Julian Hammer
|
|
140
|
+
|
|
141
|
+
Released under the terms of the Apache License 2.0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
.. _pretix: https://github.com/pretix/pretix
|
|
146
|
+
.. _pretix development setup: https://docs.pretix.eu/en/latest/development/setup.html
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
pretix Mailchimp Newsletter
|
|
2
|
+
===========================
|
|
3
|
+
|
|
4
|
+
This is a plugin for `pretix`_.
|
|
5
|
+
|
|
6
|
+
Mailchimp newsletter subscription with booking (opt-in).
|
|
7
|
+
|
|
8
|
+
Behavior
|
|
9
|
+
--------
|
|
10
|
+
|
|
11
|
+
* Activated on organizer level.
|
|
12
|
+
* Applies to all events of the organizer by default.
|
|
13
|
+
* Can only be disabled per event.
|
|
14
|
+
* Shows an optional checkbox in the booking contact form.
|
|
15
|
+
* Also shows an optional checkbox in the attendee/question form for every personalized order position, but only if the
|
|
16
|
+
event asks for email addresses per ticket.
|
|
17
|
+
* If no checkbox is selected, nothing is sent to Mailchimp.
|
|
18
|
+
* If selected, the contact is created or updated in the configured Mailchimp audience.
|
|
19
|
+
* The contact is tagged with the main pretix event, not the subevent/date.
|
|
20
|
+
* Existing contacts are not overwritten unnecessarily; the event tag is added.
|
|
21
|
+
* If booking contact and attendee use the same email address in one order, the plugin avoids duplicate Mailchimp calls.
|
|
22
|
+
|
|
23
|
+
Default public text::
|
|
24
|
+
|
|
25
|
+
ZAMgefasst, der Newsletter 1x pro Monat aus dem ZAM
|
|
26
|
+
|
|
27
|
+
Installation
|
|
28
|
+
------------
|
|
29
|
+
|
|
30
|
+
Inside your pretix virtualenv::
|
|
31
|
+
|
|
32
|
+
pip install -e .
|
|
33
|
+
|
|
34
|
+
Then restart pretix and enable the plugin on organizer level.
|
|
35
|
+
|
|
36
|
+
Configuration
|
|
37
|
+
-------------
|
|
38
|
+
|
|
39
|
+
Go to the plugin settings on organizer level and configure:
|
|
40
|
+
|
|
41
|
+
* Mailchimp API key
|
|
42
|
+
* Mailchimp Audience/List ID
|
|
43
|
+
* status for new contacts (``pending`` recommended for double opt-in)
|
|
44
|
+
* tag template
|
|
45
|
+
* public checkbox label, with translations
|
|
46
|
+
* public help text, with translations
|
|
47
|
+
|
|
48
|
+
Per event, go to event settings and use **Newsletter opt-in deaktivieren** if the opt-in should not be shown for that
|
|
49
|
+
event.
|
|
50
|
+
|
|
51
|
+
Opt-in behavior
|
|
52
|
+
---------------
|
|
53
|
+
|
|
54
|
+
The plugin stores and evaluates two independent opt-ins:
|
|
55
|
+
|
|
56
|
+
* booking contact opt-in: saved in ``order.meta_info["contact_form_data"]``
|
|
57
|
+
* attendee/position opt-in: saved in ``position.meta_info["question_form_data"]``
|
|
58
|
+
|
|
59
|
+
This matters for events with non-personalized products: If pretix does not ask for attendee name/email per position, the
|
|
60
|
+
booking contact can still explicitly subscribe.
|
|
61
|
+
|
|
62
|
+
Mailchimp tag template placeholders
|
|
63
|
+
-----------------------------------
|
|
64
|
+
|
|
65
|
+
The tag template can use:
|
|
66
|
+
|
|
67
|
+
* ``{organizer}`` - organizer slug
|
|
68
|
+
* ``{event_slug}`` - event slug
|
|
69
|
+
* ``{event}`` - event name
|
|
70
|
+
* ``{event_id}`` - internal event id
|
|
71
|
+
|
|
72
|
+
Example::
|
|
73
|
+
|
|
74
|
+
ZAM Veranstaltung: {event}
|
|
75
|
+
|
|
76
|
+
or more stable::
|
|
77
|
+
|
|
78
|
+
pretix:{organizer}:{event_slug}
|
|
79
|
+
|
|
80
|
+
Privacy note
|
|
81
|
+
------------
|
|
82
|
+
|
|
83
|
+
This plugin only transfers a contact after explicit opt-in. For new Mailchimp contacts, ``pending`` is the recommended
|
|
84
|
+
default so Mailchimp sends a confirmation email. Existing unsubscribed contacts are not forcefully resubscribed by
|
|
85
|
+
default; they only receive the event tag.
|
|
86
|
+
|
|
87
|
+
Development setup
|
|
88
|
+
-----------------
|
|
89
|
+
|
|
90
|
+
1. Make sure that you have a working `pretix development setup`_.
|
|
91
|
+
|
|
92
|
+
2. Clone this repository.
|
|
93
|
+
|
|
94
|
+
3. Activate the virtual environment you use for pretix development.
|
|
95
|
+
|
|
96
|
+
4. Execute ``python setup.py develop`` within this directory to register this application with pretix's plugin registry.
|
|
97
|
+
|
|
98
|
+
5. Execute ``make`` within this directory to compile translations.
|
|
99
|
+
|
|
100
|
+
6. Restart your local pretix server. You can now use the plugin from this repository for your events by enabling it in
|
|
101
|
+
the 'plugins' tab in the settings.
|
|
102
|
+
|
|
103
|
+
This plugin has CI set up to enforce a few code style rules. To check locally, you need these packages installed::
|
|
104
|
+
|
|
105
|
+
pip install flake8 isort black
|
|
106
|
+
|
|
107
|
+
To check your plugin for rule violations, run::
|
|
108
|
+
|
|
109
|
+
black --check .
|
|
110
|
+
isort -c .
|
|
111
|
+
flake8 .
|
|
112
|
+
|
|
113
|
+
You can auto-fix some of these issues by running::
|
|
114
|
+
|
|
115
|
+
isort .
|
|
116
|
+
black .
|
|
117
|
+
|
|
118
|
+
To automatically check for these issues before you commit, you can run ``.install-hooks``.
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
License
|
|
122
|
+
-------
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
Copyright 2026 Julian Hammer
|
|
126
|
+
|
|
127
|
+
Released under the terms of the Apache License 2.0
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
.. _pretix: https://github.com/pretix/pretix
|
|
132
|
+
.. _pretix development setup: https://docs.pretix.eu/en/latest/development/setup.html
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from pretix.base.plugins import PLUGIN_LEVEL_ORGANIZER, PluginConfig
|
|
5
|
+
except ImportError as exc: # pragma: no cover
|
|
6
|
+
raise RuntimeError("Please use pretix 2025.7 or above to run this plugin.") from exc
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PluginApp(PluginConfig):
|
|
10
|
+
name = "pretix_mailchimp"
|
|
11
|
+
verbose_name = _("Mailchimp newsletter opt-in")
|
|
12
|
+
|
|
13
|
+
class PretixPluginMeta:
|
|
14
|
+
name = _("Mailchimp newsletter opt-in")
|
|
15
|
+
author = _("Betreiberverein ZAM e.V.")
|
|
16
|
+
version = "0.2.0"
|
|
17
|
+
category = "INTEGRATION"
|
|
18
|
+
description = _(
|
|
19
|
+
"Adds newsletter opt-in fields for booking contacts and attendees during checkout "
|
|
20
|
+
"and syncs opted-in contacts to a Mailchimp audience with a tag for the main event."
|
|
21
|
+
)
|
|
22
|
+
visible = True
|
|
23
|
+
restricted = False
|
|
24
|
+
compatibility = "pretix>=2025.7"
|
|
25
|
+
level = PLUGIN_LEVEL_ORGANIZER
|
|
26
|
+
settings_links = [
|
|
27
|
+
(
|
|
28
|
+
(_("Mailchimp newsletter"),),
|
|
29
|
+
"plugins:pretix_mailchimp:settings",
|
|
30
|
+
{},
|
|
31
|
+
)
|
|
32
|
+
]
|
|
33
|
+
navigation_links = []
|
|
34
|
+
|
|
35
|
+
def ready(self):
|
|
36
|
+
from . import signals # noqa
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from i18nfield.strings import LazyI18nString
|
|
2
|
+
from pretix.base.settings import settings_hierarkey
|
|
3
|
+
|
|
4
|
+
DEFAULT_MAILCHIMP_LABEL = LazyI18nString(
|
|
5
|
+
{
|
|
6
|
+
"de": "ZAMgefasst abonnieren",
|
|
7
|
+
"de-informal": "ZAMgefasst abonnieren",
|
|
8
|
+
"en": "Subscribe to ZAMgefasst",
|
|
9
|
+
}
|
|
10
|
+
)
|
|
11
|
+
DEFAULT_MAILCHIMP_HELP_TEXT = LazyI18nString(
|
|
12
|
+
{
|
|
13
|
+
"de": "ZAMgefasst, der Newsletter 1x pro Monat aus dem ZAM",
|
|
14
|
+
"de-informal": "ZAMgefasst, der Newsletter 1x pro Monat aus dem ZAM",
|
|
15
|
+
"en": "ZAMgefasst, the monthly newsletter from ZAM",
|
|
16
|
+
}
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Organizer-level settings
|
|
20
|
+
settings_hierarkey.add_default("mailchimp_api_key", "", str)
|
|
21
|
+
settings_hierarkey.add_default("mailchimp_audience_id", "", str)
|
|
22
|
+
settings_hierarkey.add_default("mailchimp_status_if_new", "pending", str)
|
|
23
|
+
settings_hierarkey.add_default(
|
|
24
|
+
"mailchimp_tag_template",
|
|
25
|
+
"ZAM Veranstaltung: {event}",
|
|
26
|
+
str,
|
|
27
|
+
)
|
|
28
|
+
settings_hierarkey.add_default(
|
|
29
|
+
"mailchimp_label",
|
|
30
|
+
DEFAULT_MAILCHIMP_LABEL,
|
|
31
|
+
LazyI18nString,
|
|
32
|
+
)
|
|
33
|
+
settings_hierarkey.add_default(
|
|
34
|
+
"mailchimp_help_text",
|
|
35
|
+
DEFAULT_MAILCHIMP_HELP_TEXT,
|
|
36
|
+
LazyI18nString,
|
|
37
|
+
)
|
|
38
|
+
settings_hierarkey.add_default("mailchimp_timeout", 8, int)
|
|
39
|
+
|
|
40
|
+
# Event-level opt-out. Default: active.
|
|
41
|
+
settings_hierarkey.add_default("mailchimp_disabled", False, bool)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from django import forms
|
|
2
|
+
from django.utils.translation import gettext_lazy as _
|
|
3
|
+
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class OrganizerMailchimpSettingsForm(forms.Form):
|
|
7
|
+
api_key = forms.CharField(
|
|
8
|
+
label=_("Mailchimp API key"),
|
|
9
|
+
required=False,
|
|
10
|
+
widget=forms.PasswordInput(render_value=False),
|
|
11
|
+
help_text=_(
|
|
12
|
+
"Required for the first configuration. Leave empty later to keep the existing key. "
|
|
13
|
+
"The data center is read from the suffix of the key, e.g. us21."
|
|
14
|
+
),
|
|
15
|
+
)
|
|
16
|
+
audience_id = forms.CharField(
|
|
17
|
+
label=_("Mailchimp Audience/List ID"),
|
|
18
|
+
required=True,
|
|
19
|
+
help_text=_("The Mailchimp audience ID, also called list ID."),
|
|
20
|
+
)
|
|
21
|
+
status_if_new = forms.ChoiceField(
|
|
22
|
+
label=_("Status for new contacts"),
|
|
23
|
+
choices=[
|
|
24
|
+
("pending", _("pending – double opt-in confirmation email")),
|
|
25
|
+
("subscribed", _("subscribed – subscribe immediately")),
|
|
26
|
+
],
|
|
27
|
+
help_text=_(
|
|
28
|
+
"For GDPR-sensitive setups, 'pending' is usually the safer default. "
|
|
29
|
+
"Existing contacts are not forcefully resubscribed by this plugin."
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
tag_template = forms.CharField(
|
|
33
|
+
label=_("Mailchimp tag template"),
|
|
34
|
+
required=True,
|
|
35
|
+
help_text=_(
|
|
36
|
+
"Available placeholders: {organizer}, {event_slug}, {event}, {event_id}. "
|
|
37
|
+
"The tag is based on the main event, not the subevent/date."
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
label = I18nFormField(
|
|
41
|
+
label=_("Public checkbox label"),
|
|
42
|
+
required=True,
|
|
43
|
+
widget=I18nTextInput,
|
|
44
|
+
)
|
|
45
|
+
help_text = I18nFormField(
|
|
46
|
+
label=_("Public checkbox help text"),
|
|
47
|
+
required=False,
|
|
48
|
+
widget=I18nTextarea,
|
|
49
|
+
widget_kwargs={"attrs": {"rows": 3}},
|
|
50
|
+
)
|
|
51
|
+
timeout = forms.IntegerField(
|
|
52
|
+
label=_("Mailchimp request timeout in seconds"),
|
|
53
|
+
required=True,
|
|
54
|
+
min_value=1,
|
|
55
|
+
max_value=30,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def __init__(self, *args, existing_api_key=False, **kwargs):
|
|
59
|
+
super().__init__(*args, **kwargs)
|
|
60
|
+
self.existing_api_key = existing_api_key
|
|
61
|
+
if not existing_api_key:
|
|
62
|
+
self.fields["api_key"].required = True
|
|
63
|
+
|
|
64
|
+
def clean_api_key(self):
|
|
65
|
+
value = (self.cleaned_data.get("api_key") or "").strip()
|
|
66
|
+
if value and "-" not in value:
|
|
67
|
+
raise forms.ValidationError(
|
|
68
|
+
_(
|
|
69
|
+
"Mailchimp API keys usually end with a data center suffix like '-us21'."
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
if not value and not self.existing_api_key:
|
|
73
|
+
raise forms.ValidationError(_("Please enter a Mailchimp API key."))
|
|
74
|
+
return value
|
|
75
|
+
|
|
76
|
+
def clean_tag_template(self):
|
|
77
|
+
value = (self.cleaned_data.get("tag_template") or "").strip()
|
|
78
|
+
if not value:
|
|
79
|
+
raise forms.ValidationError(_("Please enter a tag template."))
|
|
80
|
+
try:
|
|
81
|
+
value.format(
|
|
82
|
+
organizer="org",
|
|
83
|
+
event_slug="event",
|
|
84
|
+
event="Event name",
|
|
85
|
+
event_id=1,
|
|
86
|
+
)
|
|
87
|
+
except KeyError as exc:
|
|
88
|
+
raise forms.ValidationError(
|
|
89
|
+
_("Unknown placeholder in tag template: {placeholder}").format(
|
|
90
|
+
placeholder="{" + str(exc.args[0]) + "}"
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
raise forms.ValidationError(str(exc))
|
|
95
|
+
return value
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class EventMailchimpSettingsForm(forms.Form):
|
|
99
|
+
disabled = forms.BooleanField(
|
|
100
|
+
label=_("Disable newsletter opt-in for this event"),
|
|
101
|
+
required=False,
|
|
102
|
+
help_text=_(
|
|
103
|
+
"The organizer-level plugin is active by default for all events. "
|
|
104
|
+
"Check this only if this event should not show the Mailchimp opt-in."
|
|
105
|
+
),
|
|
106
|
+
)
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
msgid ""
|
|
2
|
+
msgstr ""
|
|
3
|
+
"Project-Id-Version: \n"
|
|
4
|
+
"Report-Msgid-Bugs-To: \n"
|
|
5
|
+
"POT-Creation-Date: 2017-03-07 19:01+0100\n"
|
|
6
|
+
"PO-Revision-Date: \n"
|
|
7
|
+
"Last-Translator: Julian Hammer\n"
|
|
8
|
+
"Language-Team: \n"
|
|
9
|
+
"Language: de\n"
|
|
10
|
+
"MIME-Version: 1.0\n"
|
|
11
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
12
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
msgid ""
|
|
2
|
+
msgstr ""
|
|
3
|
+
"Project-Id-Version: \n"
|
|
4
|
+
"Report-Msgid-Bugs-To: \n"
|
|
5
|
+
"POT-Creation-Date: 2017-03-07 19:01+0100\n"
|
|
6
|
+
"PO-Revision-Date: \n"
|
|
7
|
+
"Last-Translator: Julian Hammer\n"
|
|
8
|
+
"Language-Team: \n"
|
|
9
|
+
"Language: de\n"
|
|
10
|
+
"MIME-Version: 1.0\n"
|
|
11
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
12
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import requests
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MailchimpConfigurationError(Exception):
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MailchimpAPIError(Exception):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class MailchimpConfig:
|
|
18
|
+
api_key: str
|
|
19
|
+
audience_id: str
|
|
20
|
+
status_if_new: str = "pending"
|
|
21
|
+
timeout: int = 8
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def data_center(self) -> str:
|
|
25
|
+
if "-" not in self.api_key:
|
|
26
|
+
raise MailchimpConfigurationError(
|
|
27
|
+
"Mailchimp API key does not contain a data center suffix."
|
|
28
|
+
)
|
|
29
|
+
return self.api_key.rsplit("-", 1)[1]
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def base_url(self) -> str:
|
|
33
|
+
return f"https://{self.data_center}.api.mailchimp.com/3.0"
|
|
34
|
+
|
|
35
|
+
def subscriber_hash(self, email: str) -> str:
|
|
36
|
+
return hashlib.md5(email.strip().lower().encode("utf-8")).hexdigest()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MailchimpClient:
|
|
40
|
+
def __init__(self, config: MailchimpConfig):
|
|
41
|
+
self.config = config
|
|
42
|
+
|
|
43
|
+
def _request(self, method: str, path: str, json: Optional[dict] = None):
|
|
44
|
+
url = f"{self.config.base_url}{path}"
|
|
45
|
+
try:
|
|
46
|
+
response = requests.request(
|
|
47
|
+
method,
|
|
48
|
+
url,
|
|
49
|
+
auth=("pretix", self.config.api_key),
|
|
50
|
+
json=json,
|
|
51
|
+
timeout=self.config.timeout,
|
|
52
|
+
headers={"Content-Type": "application/json"},
|
|
53
|
+
)
|
|
54
|
+
except requests.RequestException as exc:
|
|
55
|
+
raise MailchimpAPIError(str(exc)) from exc
|
|
56
|
+
|
|
57
|
+
if response.status_code >= 400:
|
|
58
|
+
try:
|
|
59
|
+
payload = response.json()
|
|
60
|
+
except ValueError:
|
|
61
|
+
payload = {"detail": response.text}
|
|
62
|
+
raise MailchimpAPIError(
|
|
63
|
+
f"Mailchimp API returned {response.status_code}: {payload}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
if response.status_code == 204:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
if response.content:
|
|
70
|
+
return response.json()
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
def upsert_member_without_resubscribe(self, email: str):
|
|
74
|
+
"""Create the contact if missing, but do not force-resubscribe existing contacts."""
|
|
75
|
+
subscriber_hash = self.config.subscriber_hash(email)
|
|
76
|
+
path = f"/lists/{self.config.audience_id}/members/{subscriber_hash}"
|
|
77
|
+
return self._request(
|
|
78
|
+
"PUT",
|
|
79
|
+
path,
|
|
80
|
+
json={
|
|
81
|
+
"email_address": email.strip(),
|
|
82
|
+
"status_if_new": self.config.status_if_new,
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def add_tag(self, email: str, tag: str):
|
|
87
|
+
subscriber_hash = self.config.subscriber_hash(email)
|
|
88
|
+
path = f"/lists/{self.config.audience_id}/members/{subscriber_hash}/tags"
|
|
89
|
+
return self._request(
|
|
90
|
+
"POST",
|
|
91
|
+
path,
|
|
92
|
+
json={"tags": [{"name": tag.strip(), "status": "active"}]},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def upsert_and_tag(self, email: str, tag: str):
|
|
96
|
+
self.upsert_member_without_resubscribe(email)
|
|
97
|
+
self.add_tag(email, tag)
|