django-ctct 0.0.1.dev4__tar.gz → 0.0.1.dev6__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.
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/PKG-INFO +26 -13
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/README.md +20 -10
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/django_ctct/admin.py +29 -38
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/django_ctct/apps.py +0 -10
- django_ctct-0.0.1.dev6/django_ctct/management/commands/__init__.py +0 -0
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/django_ctct/management/commands/import_ctct.py +45 -95
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/django_ctct/managers.py +116 -118
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/django_ctct/migrations/0001_initial.py +24 -6
- django_ctct-0.0.1.dev6/django_ctct/migrations/__init__.py +0 -0
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/django_ctct/models.py +137 -112
- django_ctct-0.0.1.dev6/django_ctct/signals.py +41 -0
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/django_ctct/utils.py +5 -0
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/django_ctct/views.py +0 -1
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/pyproject.toml +3 -2
- django_ctct-0.0.1.dev4/django_ctct/signals.py +0 -93
- django_ctct-0.0.1.dev4/django_ctct/vendor.py +0 -108
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/LICENSE +0 -0
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/django_ctct/__init__.py +0 -0
- {django_ctct-0.0.1.dev4/django_ctct/migrations → django_ctct-0.0.1.dev6/django_ctct/management}/__init__.py +0 -0
- {django_ctct-0.0.1.dev4 → django_ctct-0.0.1.dev6}/django_ctct/urls.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: django-ctct
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev6
|
|
4
4
|
Summary: A Django interface for the Constant Contact API
|
|
5
5
|
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Author: Geoffrey Eisenbarth
|
|
7
8
|
Author-email: geoffrey.eisenbarth@gmail.com
|
|
8
9
|
Requires-Python: >=3.10
|
|
@@ -12,7 +13,9 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
12
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: django (>=4.2)
|
|
18
|
+
Requires-Dist: mypy (>=1.19.0,<2.0.0)
|
|
16
19
|
Requires-Dist: pyjwt[crypto] (>=2.10.1,<3.0.0)
|
|
17
20
|
Requires-Dist: ratelimit (>=2.2.1,<3.0.0)
|
|
18
21
|
Requires-Dist: requests (>=2.32.3,<3.0.0)
|
|
@@ -25,7 +28,9 @@ Description-Content-Type: text/markdown
|
|
|
25
28
|
|
|
26
29
|
This Django app provides a seamless interface to the Constant Contact API, allowing you to manage contacts, email campaigns, and other Constant Contact functionalities directly from your Django project.
|
|
27
30
|
|
|
28
|
-
**Warning:** This package is under active development.
|
|
31
|
+
**Warning:** This package is under active development.
|
|
32
|
+
While it is our intention to develop with a consistent API going forward, we will not make promises until a later version is released.
|
|
33
|
+
|
|
29
34
|
|
|
30
35
|
## Installation
|
|
31
36
|
|
|
@@ -33,6 +38,7 @@ This Django app provides a seamless interface to the Constant Contact API, allow
|
|
|
33
38
|
pip install django-ctct
|
|
34
39
|
```
|
|
35
40
|
|
|
41
|
+
|
|
36
42
|
## Configuration
|
|
37
43
|
|
|
38
44
|
1) **Add to `INSTALLED_APPS`:**
|
|
@@ -61,6 +67,7 @@ urlpatterns = [
|
|
|
61
67
|
# ... other URL patterns
|
|
62
68
|
]
|
|
63
69
|
```
|
|
70
|
+
|
|
64
71
|
3) **ConstantContact API Credentials:**
|
|
65
72
|
|
|
66
73
|
Head to https://app.constantcontact.com/pages/dma/portal/ to set up your application with ConstantContact.
|
|
@@ -110,17 +117,19 @@ CTCT_SYNC_ADMIN = False # Django admin CRUD operations will sync with ctct
|
|
|
110
117
|
* `CTCT_PREVIEW_MESSAGE` will be blank by default.
|
|
111
118
|
|
|
112
119
|
3) **Run Migrations:**
|
|
120
|
+
|
|
113
121
|
```bash
|
|
114
122
|
> ./manage.py makemigrations
|
|
115
123
|
> ./manage.py migrate
|
|
116
124
|
```
|
|
117
125
|
|
|
118
|
-
4) **Create
|
|
126
|
+
4) **Create Authentication Token:**
|
|
119
127
|
|
|
120
128
|
After the app has been installed and configured, you must generate your first auth token:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
129
|
+
|
|
130
|
+
* Open up the `CTCT_REDIRECT_URI` address in your browser
|
|
131
|
+
* Use your ConstantContact credentials to log in
|
|
132
|
+
* From this point forward `django-ctct` should use refresh tokens, so no need to manually log in again
|
|
124
133
|
|
|
125
134
|
|
|
126
135
|
## Usage
|
|
@@ -130,12 +139,16 @@ If you wish to import data from ConstantContact.com into your local database (re
|
|
|
130
139
|
> ./manage.py import_ctct
|
|
131
140
|
```
|
|
132
141
|
|
|
133
|
-
You will be asked before each model type is imported.
|
|
142
|
+
You will be asked before each model type is imported.
|
|
143
|
+
**Note** ConstantContact does not provide a bulk API endpoint for fetching Campaign Activities, so depending on the size of your account, this might take some time and possible put you over their 10,000 request per day limit if you run it regularly.
|
|
134
144
|
|
|
135
|
-
Since ConstantContact does not offer any webhooks, you will need to set up a cron job if you want your account to remain syncronized with ConstantContact's database.
|
|
145
|
+
Since ConstantContact does not offer any webhooks, you will need to set up a cron job if you want your account to remain syncronized with ConstantContact's database.
|
|
146
|
+
You can use the `--no-input` flag to bypass the interactive questions.
|
|
147
|
+
The `--stats-only` flag is useful for running a cron job to keep EmailCampaign statistics updated.
|
|
136
148
|
|
|
137
149
|
If you wish to use the Django admin to interact with ConstantContact, you must explicitly set the `CTCT_USE_ADMIN` and `CTCT_SYNC_ADMIN` settings to `True`.
|
|
138
150
|
|
|
151
|
+
|
|
139
152
|
## Testing
|
|
140
153
|
|
|
141
154
|
To install dev dependencies:
|
|
@@ -155,13 +168,13 @@ To run tests:
|
|
|
155
168
|
> poetry run coverage report
|
|
156
169
|
```
|
|
157
170
|
|
|
171
|
+
|
|
158
172
|
## Contributing
|
|
159
173
|
|
|
160
174
|
Once version 0.0.1 is released on PyPI, we hope to implement the following new features (in no particular order):
|
|
161
175
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
* Add `models.CheckConstraint` and `models.UniqueConstraint` constraints that are currently commented out.
|
|
176
|
+
* Background task support using `django-tasks` (which hopefully will merge into Django). This will be controlled by the `CTCT_ENQUEUE_DEFAULT` setting.
|
|
177
|
+
* Add `models.CheckConstraint` and `models.UniqueConstraint` constraints that are currently commented out.
|
|
165
178
|
|
|
166
179
|
|
|
167
180
|
I'm always open to new suggestions, so please reach out on GitHub: https://github.com/geoffrey-eisenbarth/django-ctct/
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
This Django app provides a seamless interface to the Constant Contact API, allowing you to manage contacts, email campaigns, and other Constant Contact functionalities directly from your Django project.
|
|
6
6
|
|
|
7
|
-
**Warning:** This package is under active development.
|
|
7
|
+
**Warning:** This package is under active development.
|
|
8
|
+
While it is our intention to develop with a consistent API going forward, we will not make promises until a later version is released.
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
## Installation
|
|
10
12
|
|
|
@@ -12,6 +14,7 @@ This Django app provides a seamless interface to the Constant Contact API, allow
|
|
|
12
14
|
pip install django-ctct
|
|
13
15
|
```
|
|
14
16
|
|
|
17
|
+
|
|
15
18
|
## Configuration
|
|
16
19
|
|
|
17
20
|
1) **Add to `INSTALLED_APPS`:**
|
|
@@ -40,6 +43,7 @@ urlpatterns = [
|
|
|
40
43
|
# ... other URL patterns
|
|
41
44
|
]
|
|
42
45
|
```
|
|
46
|
+
|
|
43
47
|
3) **ConstantContact API Credentials:**
|
|
44
48
|
|
|
45
49
|
Head to https://app.constantcontact.com/pages/dma/portal/ to set up your application with ConstantContact.
|
|
@@ -89,17 +93,19 @@ CTCT_SYNC_ADMIN = False # Django admin CRUD operations will sync with ctct
|
|
|
89
93
|
* `CTCT_PREVIEW_MESSAGE` will be blank by default.
|
|
90
94
|
|
|
91
95
|
3) **Run Migrations:**
|
|
96
|
+
|
|
92
97
|
```bash
|
|
93
98
|
> ./manage.py makemigrations
|
|
94
99
|
> ./manage.py migrate
|
|
95
100
|
```
|
|
96
101
|
|
|
97
|
-
4) **Create
|
|
102
|
+
4) **Create Authentication Token:**
|
|
98
103
|
|
|
99
104
|
After the app has been installed and configured, you must generate your first auth token:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
|
|
106
|
+
* Open up the `CTCT_REDIRECT_URI` address in your browser
|
|
107
|
+
* Use your ConstantContact credentials to log in
|
|
108
|
+
* From this point forward `django-ctct` should use refresh tokens, so no need to manually log in again
|
|
103
109
|
|
|
104
110
|
|
|
105
111
|
## Usage
|
|
@@ -109,12 +115,16 @@ If you wish to import data from ConstantContact.com into your local database (re
|
|
|
109
115
|
> ./manage.py import_ctct
|
|
110
116
|
```
|
|
111
117
|
|
|
112
|
-
You will be asked before each model type is imported.
|
|
118
|
+
You will be asked before each model type is imported.
|
|
119
|
+
**Note** ConstantContact does not provide a bulk API endpoint for fetching Campaign Activities, so depending on the size of your account, this might take some time and possible put you over their 10,000 request per day limit if you run it regularly.
|
|
113
120
|
|
|
114
|
-
Since ConstantContact does not offer any webhooks, you will need to set up a cron job if you want your account to remain syncronized with ConstantContact's database.
|
|
121
|
+
Since ConstantContact does not offer any webhooks, you will need to set up a cron job if you want your account to remain syncronized with ConstantContact's database.
|
|
122
|
+
You can use the `--no-input` flag to bypass the interactive questions.
|
|
123
|
+
The `--stats-only` flag is useful for running a cron job to keep EmailCampaign statistics updated.
|
|
115
124
|
|
|
116
125
|
If you wish to use the Django admin to interact with ConstantContact, you must explicitly set the `CTCT_USE_ADMIN` and `CTCT_SYNC_ADMIN` settings to `True`.
|
|
117
126
|
|
|
127
|
+
|
|
118
128
|
## Testing
|
|
119
129
|
|
|
120
130
|
To install dev dependencies:
|
|
@@ -134,13 +144,13 @@ To run tests:
|
|
|
134
144
|
> poetry run coverage report
|
|
135
145
|
```
|
|
136
146
|
|
|
147
|
+
|
|
137
148
|
## Contributing
|
|
138
149
|
|
|
139
150
|
Once version 0.0.1 is released on PyPI, we hope to implement the following new features (in no particular order):
|
|
140
151
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* Add `models.CheckConstraint` and `models.UniqueConstraint` constraints that are currently commented out.
|
|
152
|
+
* Background task support using `django-tasks` (which hopefully will merge into Django). This will be controlled by the `CTCT_ENQUEUE_DEFAULT` setting.
|
|
153
|
+
* Add `models.CheckConstraint` and `models.UniqueConstraint` constraints that are currently commented out.
|
|
144
154
|
|
|
145
155
|
|
|
146
156
|
I'm always open to new suggestions, so please reach out on GitHub: https://github.com/geoffrey-eisenbarth/django-ctct/
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import functools
|
|
2
|
-
from typing import TypeVar, ParamSpec, Generic,
|
|
2
|
+
from typing import TypeVar, ParamSpec, Generic, Callable, Iterable
|
|
3
3
|
from requests.exceptions import HTTPError
|
|
4
4
|
|
|
5
5
|
from django import forms
|
|
6
6
|
from django.conf import settings
|
|
7
7
|
from django.contrib import admin, messages
|
|
8
8
|
from django.contrib.auth import get_user_model
|
|
9
|
-
from django.db.models import signals
|
|
10
9
|
from django.db.models import Model, QuerySet, When, Case, F, FloatField
|
|
11
10
|
from django.db.models.functions import Cast
|
|
12
11
|
from django.forms import ModelForm, BaseFormSet
|
|
@@ -24,7 +23,6 @@ from django_ctct.models import (
|
|
|
24
23
|
EmailCampaign, CampaignActivity, CampaignSummary,
|
|
25
24
|
)
|
|
26
25
|
from django_ctct.signals import remote_save, remote_delete
|
|
27
|
-
from django_ctct.vendor import mute_signals
|
|
28
26
|
|
|
29
27
|
|
|
30
28
|
P = ParamSpec('P')
|
|
@@ -51,7 +49,7 @@ def catch_api_errors(func: Callable[P, None]) -> Callable[P, None]:
|
|
|
51
49
|
if getattr(settings, 'CTCT_RAISE_FOR_API', False):
|
|
52
50
|
raise e
|
|
53
51
|
else:
|
|
54
|
-
self, request, *
|
|
52
|
+
self, request, *x = args
|
|
55
53
|
assert isinstance(self, admin.ModelAdmin)
|
|
56
54
|
assert isinstance(request, HttpRequest)
|
|
57
55
|
self.message_user(
|
|
@@ -81,7 +79,7 @@ class ViewModelAdmin(admin.ModelAdmin[Model]):
|
|
|
81
79
|
def has_add_permission(
|
|
82
80
|
self,
|
|
83
81
|
request: HttpRequest,
|
|
84
|
-
obj:
|
|
82
|
+
obj: Model | None = None,
|
|
85
83
|
) -> bool:
|
|
86
84
|
"""Prevent creation in the Django admin."""
|
|
87
85
|
return False
|
|
@@ -89,7 +87,7 @@ class ViewModelAdmin(admin.ModelAdmin[Model]):
|
|
|
89
87
|
def has_change_permission(
|
|
90
88
|
self,
|
|
91
89
|
request: HttpRequest,
|
|
92
|
-
obj:
|
|
90
|
+
obj: Model | None = None,
|
|
93
91
|
) -> bool:
|
|
94
92
|
"""Prevent updates in the Django admin."""
|
|
95
93
|
return False
|
|
@@ -97,7 +95,7 @@ class ViewModelAdmin(admin.ModelAdmin[Model]):
|
|
|
97
95
|
def get_readonly_fields(
|
|
98
96
|
self,
|
|
99
97
|
request: HttpRequest,
|
|
100
|
-
obj:
|
|
98
|
+
obj: Model | None = None,
|
|
101
99
|
) -> tuple[str, ...]:
|
|
102
100
|
"""Prevent updates in the Django admin."""
|
|
103
101
|
if obj is not None:
|
|
@@ -113,7 +111,7 @@ class ViewModelAdmin(admin.ModelAdmin[Model]):
|
|
|
113
111
|
def has_delete_permission(
|
|
114
112
|
self,
|
|
115
113
|
request: HttpRequest,
|
|
116
|
-
obj:
|
|
114
|
+
obj: Model | None = None,
|
|
117
115
|
) -> bool:
|
|
118
116
|
"""Allow superusers to delete objects."""
|
|
119
117
|
return request.user.is_superuser
|
|
@@ -124,35 +122,30 @@ class RemoteModelAdmin(
|
|
|
124
122
|
):
|
|
125
123
|
"""Facilitate remote saving and deleting."""
|
|
126
124
|
|
|
127
|
-
|
|
128
|
-
@property
|
|
129
|
-
def remote_sync(self) -> bool:
|
|
130
|
-
sync_admin = getattr(settings, 'CTCT_SYNC_ADMIN', False)
|
|
131
|
-
sync_signals = getattr(settings, 'CTCT_SYNC_SIGNALS', False)
|
|
132
|
-
return sync_admin and not sync_signals
|
|
125
|
+
sync_admin: bool = getattr(settings, 'CTCT_SYNC_ADMIN', False)
|
|
133
126
|
|
|
127
|
+
# ChangeView
|
|
134
128
|
@catch_api_errors
|
|
135
129
|
def delete_model(self, request: HttpRequest, obj: Model) -> None:
|
|
136
130
|
obj.delete()
|
|
137
|
-
if self.
|
|
131
|
+
if self.sync_admin:
|
|
138
132
|
remote_delete(sender=self.model, instance=obj)
|
|
139
133
|
|
|
140
134
|
@catch_api_errors
|
|
141
135
|
def delete_queryset(
|
|
142
136
|
self,
|
|
143
137
|
request: HttpRequest,
|
|
144
|
-
queryset: QuerySet[
|
|
138
|
+
queryset: QuerySet[E],
|
|
145
139
|
) -> None:
|
|
146
|
-
if self.
|
|
140
|
+
if self.sync_admin:
|
|
147
141
|
queryset.model.remote.bulk_delete(queryset)
|
|
148
|
-
|
|
149
|
-
queryset.delete()
|
|
142
|
+
queryset.delete()
|
|
150
143
|
|
|
151
144
|
@catch_api_errors
|
|
152
145
|
def save_related(
|
|
153
146
|
self,
|
|
154
147
|
request: HttpRequest,
|
|
155
|
-
form: ModelForm[
|
|
148
|
+
form: ModelForm[E],
|
|
156
149
|
formsets: list[BaseFormSet[ModelForm[Model]]],
|
|
157
150
|
change: bool,
|
|
158
151
|
) -> None:
|
|
@@ -164,9 +157,8 @@ class RemoteModelAdmin(
|
|
|
164
157
|
for saving objects remotely.
|
|
165
158
|
|
|
166
159
|
"""
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
form.save_m2m()
|
|
160
|
+
# ManyToMany information is sent to CTCT in PUT call
|
|
161
|
+
form.save_m2m()
|
|
170
162
|
for formset in formsets:
|
|
171
163
|
self.save_formset(request, form, formset, change=change)
|
|
172
164
|
self.save_remotely(request, form, formsets, change)
|
|
@@ -175,11 +167,11 @@ class RemoteModelAdmin(
|
|
|
175
167
|
def save_remotely(
|
|
176
168
|
self,
|
|
177
169
|
request: HttpRequest,
|
|
178
|
-
form: ModelForm[
|
|
170
|
+
form: ModelForm[E],
|
|
179
171
|
formsets: list[BaseFormSet[ModelForm[Model]]],
|
|
180
172
|
change: bool,
|
|
181
173
|
) -> None:
|
|
182
|
-
if self.
|
|
174
|
+
if self.sync_admin:
|
|
183
175
|
# Remote save the primary object after related objects have been saved
|
|
184
176
|
remote_save(
|
|
185
177
|
sender=self.model,
|
|
@@ -285,7 +277,7 @@ class ContactNoteInline(admin.TabularInline[ContactNote, Contact]):
|
|
|
285
277
|
def has_change_permission(
|
|
286
278
|
self,
|
|
287
279
|
request: HttpRequest,
|
|
288
|
-
obj:
|
|
280
|
+
obj: Contact | None = None, # type: ignore[override]
|
|
289
281
|
) -> bool:
|
|
290
282
|
return False
|
|
291
283
|
|
|
@@ -295,7 +287,7 @@ class ContactCustomFieldInline(
|
|
|
295
287
|
):
|
|
296
288
|
|
|
297
289
|
model = ContactCustomField
|
|
298
|
-
|
|
290
|
+
exclude = ('api_id', )
|
|
299
291
|
|
|
300
292
|
extra = 0
|
|
301
293
|
|
|
@@ -370,7 +362,7 @@ class ContactAdmin(RemoteModelAdmin[Contact]):
|
|
|
370
362
|
def get_readonly_fields(
|
|
371
363
|
self,
|
|
372
364
|
request: HttpRequest,
|
|
373
|
-
obj:
|
|
365
|
+
obj: Contact | None = None,
|
|
374
366
|
) -> list[str]:
|
|
375
367
|
readonly_fields = list(Contact.API_READONLY_FIELDS)
|
|
376
368
|
if obj and obj.opt_out_source and not request.user.is_superuser:
|
|
@@ -401,9 +393,8 @@ class ContactAdmin(RemoteModelAdmin[Contact]):
|
|
|
401
393
|
instance.author = request.user
|
|
402
394
|
instance.save()
|
|
403
395
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
formset.save_m2m()
|
|
396
|
+
# ManyToMany information is sent to CTCT in PUT call
|
|
397
|
+
formset.save_m2m()
|
|
407
398
|
|
|
408
399
|
|
|
409
400
|
class ContactNoteAuthorFilter(admin.SimpleListFilter):
|
|
@@ -416,7 +407,7 @@ class ContactNoteAuthorFilter(admin.SimpleListFilter):
|
|
|
416
407
|
self,
|
|
417
408
|
request: HttpRequest,
|
|
418
409
|
model_admin: admin.ModelAdmin[Model],
|
|
419
|
-
) ->
|
|
410
|
+
) -> Iterable[tuple[str, str]] | None:
|
|
420
411
|
authors = get_user_model().objects.exclude(notes__isnull=True)
|
|
421
412
|
return [(str(obj.id), str(obj)) for obj in authors]
|
|
422
413
|
|
|
@@ -473,7 +464,7 @@ class ContactNoteAdmin(RemoteSyncMixin, ViewModelAdmin):
|
|
|
473
464
|
def has_delete_permission(
|
|
474
465
|
self,
|
|
475
466
|
request: HttpRequest,
|
|
476
|
-
obj:
|
|
467
|
+
obj: Model | None = None,
|
|
477
468
|
) -> bool:
|
|
478
469
|
"""Allow superusers to delete Notes."""
|
|
479
470
|
return request.user.is_superuser
|
|
@@ -516,7 +507,7 @@ class CampaignActivityInline(
|
|
|
516
507
|
def get_readonly_fields(
|
|
517
508
|
self,
|
|
518
509
|
request: HttpRequest,
|
|
519
|
-
obj:
|
|
510
|
+
obj: CampaignActivity | None = None,
|
|
520
511
|
) -> list[str]:
|
|
521
512
|
readonly_fields = list(CampaignActivity.API_READONLY_FIELDS)
|
|
522
513
|
if obj and obj.current_status == 'DONE':
|
|
@@ -559,7 +550,7 @@ class EmailCampaignAdmin(RemoteModelAdmin[EmailCampaign]):
|
|
|
559
550
|
def get_readonly_fields(
|
|
560
551
|
self,
|
|
561
552
|
request: HttpRequest,
|
|
562
|
-
obj:
|
|
553
|
+
obj: EmailCampaign | None = None,
|
|
563
554
|
) -> list[str]:
|
|
564
555
|
readonly_fields = list(EmailCampaign.API_READONLY_FIELDS)
|
|
565
556
|
if obj and obj.current_status == 'DONE':
|
|
@@ -570,20 +561,20 @@ class EmailCampaignAdmin(RemoteModelAdmin[EmailCampaign]):
|
|
|
570
561
|
def save_remotely(
|
|
571
562
|
self,
|
|
572
563
|
request: HttpRequest,
|
|
573
|
-
form: ModelForm[EmailCampaign],
|
|
564
|
+
form: ModelForm[EmailCampaign],
|
|
574
565
|
formsets: list[BaseFormSet[ModelForm[Model]]],
|
|
575
566
|
change: bool,
|
|
576
567
|
) -> None:
|
|
577
|
-
if self.
|
|
568
|
+
if self.sync_admin:
|
|
578
569
|
|
|
579
570
|
campaign = form.instance
|
|
580
571
|
activity = formsets[0][0].instance
|
|
581
572
|
|
|
582
573
|
# Handle remote saving the EmailCampaign
|
|
574
|
+
# NOTE: The only EmailCampaign field that can be updated is 'name'
|
|
583
575
|
campaign_created = not change
|
|
584
576
|
campaign_updated = change and ('name' in form.changed_data)
|
|
585
577
|
if campaign_created or campaign_updated:
|
|
586
|
-
# The only EmailCampaign field that can be updated is 'name'
|
|
587
578
|
remote_save(
|
|
588
579
|
sender=self.model,
|
|
589
580
|
instance=campaign,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from django.apps import AppConfig
|
|
2
|
-
from django.db.models.signals import post_save, m2m_changed, pre_delete
|
|
3
2
|
from django.conf import settings
|
|
4
3
|
from django.core.exceptions import ImproperlyConfigured
|
|
5
4
|
from django.utils.translation import gettext_lazy as _
|
|
@@ -24,12 +23,3 @@ class CTCTConfig(AppConfig):
|
|
|
24
23
|
f"[django-ctct] {value} must be defined in settings.py."
|
|
25
24
|
)
|
|
26
25
|
raise ImproperlyConfigured(message)
|
|
27
|
-
|
|
28
|
-
# Hook up the signals
|
|
29
|
-
from django_ctct.signals import (
|
|
30
|
-
remote_save, remote_delete, remote_update_m2m
|
|
31
|
-
)
|
|
32
|
-
if getattr(settings, 'CTCT_SYNC_SIGNALS', False):
|
|
33
|
-
post_save.connect(remote_save)
|
|
34
|
-
pre_delete.connect(remote_delete)
|
|
35
|
-
m2m_changed.connect(remote_update_m2m)
|
|
File without changes
|