django-webhook-subscriber 1.0.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
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_webhook_subscriber/__init__.py +7 -1
- django_webhook_subscriber/admin.py +831 -182
- django_webhook_subscriber/apps.py +3 -20
- django_webhook_subscriber/conf.py +11 -24
- django_webhook_subscriber/delivery.py +414 -159
- django_webhook_subscriber/http.py +51 -0
- django_webhook_subscriber/management/commands/webhook.py +169 -0
- django_webhook_subscriber/management/commands/webhook_cache.py +173 -0
- django_webhook_subscriber/management/commands/webhook_logs.py +226 -0
- django_webhook_subscriber/management/commands/webhook_performance_test.py +469 -0
- django_webhook_subscriber/management/commands/webhook_send.py +96 -0
- django_webhook_subscriber/management/commands/webhook_status.py +139 -0
- django_webhook_subscriber/managers.py +36 -14
- django_webhook_subscriber/migrations/0002_remove_webhookregistry_content_type_and_more.py +192 -0
- django_webhook_subscriber/models.py +291 -114
- django_webhook_subscriber/serializers.py +16 -50
- django_webhook_subscriber/tasks.py +434 -56
- django_webhook_subscriber/tests/factories.py +40 -0
- django_webhook_subscriber/tests/settings.py +27 -8
- django_webhook_subscriber/tests/test_delivery.py +453 -190
- django_webhook_subscriber/tests/test_http.py +32 -0
- django_webhook_subscriber/tests/test_managers.py +26 -37
- django_webhook_subscriber/tests/test_models.py +341 -251
- django_webhook_subscriber/tests/test_serializers.py +22 -56
- django_webhook_subscriber/tests/test_tasks.py +477 -189
- django_webhook_subscriber/tests/test_utils.py +98 -94
- django_webhook_subscriber/utils.py +87 -69
- django_webhook_subscriber/validators.py +53 -0
- django_webhook_subscriber-2.0.0.dist-info/METADATA +774 -0
- django_webhook_subscriber-2.0.0.dist-info/RECORD +38 -0
- django_webhook_subscriber/management/commands/check_webhook_tasks.py +0 -113
- django_webhook_subscriber/management/commands/clean_webhook_logs.py +0 -65
- django_webhook_subscriber/management/commands/test_webhook.py +0 -96
- django_webhook_subscriber/signals.py +0 -152
- django_webhook_subscriber/testing.py +0 -14
- django_webhook_subscriber/tests/test_signals.py +0 -268
- django_webhook_subscriber-1.0.0.dist-info/METADATA +0 -448
- django_webhook_subscriber-1.0.0.dist-info/RECORD +0 -33
- {django_webhook_subscriber-1.0.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/WHEEL +0 -0
- {django_webhook_subscriber-1.0.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {django_webhook_subscriber-1.0.0.dist-info → django_webhook_subscriber-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"""Managers for Django Webhook Subscriber."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
|
|
3
5
|
from django.db import models
|
|
4
6
|
from django.utils import timezone
|
|
5
7
|
|
|
6
|
-
from
|
|
8
|
+
from .conf import rest_webhook_settings
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
class WebhookDeliveryLogManager(models.Manager):
|
|
@@ -13,41 +17,59 @@ class WebhookDeliveryLogManager(models.Manager):
|
|
|
13
17
|
age, and to clean up old logs based on retention settings.
|
|
14
18
|
"""
|
|
15
19
|
|
|
16
|
-
def cleanup_old_logs(self,
|
|
20
|
+
def cleanup_old_logs(self, subscription=None):
|
|
17
21
|
"""This method will cleanup old logs based on retention settings."""
|
|
18
22
|
|
|
19
23
|
# Get retention period from settings
|
|
20
|
-
days = getattr(rest_webhook_settings,
|
|
24
|
+
days = getattr(rest_webhook_settings, "LOG_RETENTION_DAYS")
|
|
21
25
|
|
|
22
26
|
# Calculate cutoff date
|
|
23
27
|
cutoff_date = timezone.now() - timezone.timedelta(days=days)
|
|
24
28
|
|
|
25
29
|
# Build query
|
|
26
30
|
query = self.filter(created_at__lt=cutoff_date)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
|
|
32
|
+
logger.debug("Cleaning up logs older than: %s...", cutoff_date)
|
|
33
|
+
|
|
34
|
+
if subscription:
|
|
35
|
+
# If a subscription is provided, filter by it
|
|
36
|
+
query = query.filter(subscription=subscription)
|
|
37
|
+
|
|
38
|
+
logger.debug("Found a total of %d old logs.", query.count())
|
|
30
39
|
|
|
31
40
|
# Delete old logs
|
|
32
|
-
query.delete()
|
|
41
|
+
deleted, _ = query.delete()
|
|
42
|
+
|
|
43
|
+
logger.info("Deleted a total of %d old logs.", deleted)
|
|
33
44
|
|
|
34
45
|
def create(self, **kwargs):
|
|
35
|
-
"""
|
|
36
|
-
|
|
46
|
+
"""
|
|
47
|
+
This method will create a new log entry, and proceed to cleanup old
|
|
48
|
+
logs if necessary.
|
|
49
|
+
"""
|
|
37
50
|
|
|
38
51
|
# create the log entry
|
|
39
52
|
log = super().create(**kwargs)
|
|
40
53
|
|
|
41
54
|
# Checking if AUTO_CLEANUP is set to True
|
|
42
|
-
auto_cleanup = getattr(rest_webhook_settings,
|
|
55
|
+
auto_cleanup = getattr(rest_webhook_settings, "AUTO_CLEANUP")
|
|
43
56
|
|
|
44
57
|
# cleanup old logs if necessary
|
|
45
|
-
if
|
|
58
|
+
if (
|
|
59
|
+
auto_cleanup
|
|
60
|
+
and "subscription" in kwargs
|
|
61
|
+
and kwargs["subscription"]
|
|
62
|
+
):
|
|
46
63
|
# Using a try/except block to prevent any errors during cleanup
|
|
47
64
|
# from affecting the log creation process
|
|
48
65
|
try:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
logger.debug(
|
|
67
|
+
"Cleaning up old logs for subscription: %s...",
|
|
68
|
+
kwargs["subscription"],
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
self.cleanup_old_logs(subscription=kwargs["subscription"])
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error("Error cleaning up old logs: %s", str(e))
|
|
52
74
|
|
|
53
75
|
return log
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Generated by Django 5.2.6 on 2025-09-29 10:38
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
import django_webhook_subscriber.utils
|
|
5
|
+
import django_webhook_subscriber.validators
|
|
6
|
+
from django.db import migrations, models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
('contenttypes', '0002_remove_content_type_name'),
|
|
13
|
+
('django_webhook_subscriber', '0001_initial'),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.RemoveField(
|
|
18
|
+
model_name='webhookregistry',
|
|
19
|
+
name='content_type',
|
|
20
|
+
),
|
|
21
|
+
migrations.RemoveField(
|
|
22
|
+
model_name='webhookdeliverylog',
|
|
23
|
+
name='webhook',
|
|
24
|
+
),
|
|
25
|
+
migrations.RemoveField(
|
|
26
|
+
model_name='webhookdeliverylog',
|
|
27
|
+
name='event_signal',
|
|
28
|
+
),
|
|
29
|
+
migrations.AddField(
|
|
30
|
+
model_name='webhookdeliverylog',
|
|
31
|
+
name='attempt_number',
|
|
32
|
+
field=models.PositiveSmallIntegerField(default=1),
|
|
33
|
+
),
|
|
34
|
+
migrations.AddField(
|
|
35
|
+
model_name='webhookdeliverylog',
|
|
36
|
+
name='delivery_duration_ms',
|
|
37
|
+
field=models.PositiveIntegerField(blank=True, null=True),
|
|
38
|
+
),
|
|
39
|
+
migrations.AddField(
|
|
40
|
+
model_name='webhookdeliverylog',
|
|
41
|
+
name='delivery_url',
|
|
42
|
+
field=models.CharField(default='', max_length=500),
|
|
43
|
+
preserve_default=False,
|
|
44
|
+
),
|
|
45
|
+
migrations.AddField(
|
|
46
|
+
model_name='webhookdeliverylog',
|
|
47
|
+
name='is_retry',
|
|
48
|
+
field=models.BooleanField(default=False),
|
|
49
|
+
),
|
|
50
|
+
migrations.AddField(
|
|
51
|
+
model_name='webhookdeliverylog',
|
|
52
|
+
name='response_headers',
|
|
53
|
+
field=models.JSONField(blank=True, default=dict),
|
|
54
|
+
),
|
|
55
|
+
migrations.AlterField(
|
|
56
|
+
model_name='webhookdeliverylog',
|
|
57
|
+
name='error_message',
|
|
58
|
+
field=models.TextField(blank=True, default=''),
|
|
59
|
+
preserve_default=False,
|
|
60
|
+
),
|
|
61
|
+
migrations.AlterField(
|
|
62
|
+
model_name='webhookdeliverylog',
|
|
63
|
+
name='payload',
|
|
64
|
+
field=models.JSONField(default=dict),
|
|
65
|
+
),
|
|
66
|
+
migrations.AlterField(
|
|
67
|
+
model_name='webhookdeliverylog',
|
|
68
|
+
name='response_body',
|
|
69
|
+
field=models.TextField(blank=True, default=None),
|
|
70
|
+
preserve_default=False,
|
|
71
|
+
),
|
|
72
|
+
migrations.AlterField(
|
|
73
|
+
model_name='webhookdeliverylog',
|
|
74
|
+
name='response_status',
|
|
75
|
+
field=models.PositiveIntegerField(blank=True, null=True),
|
|
76
|
+
),
|
|
77
|
+
migrations.CreateModel(
|
|
78
|
+
name='WebhookSubscriber',
|
|
79
|
+
fields=[
|
|
80
|
+
('id', models.AutoField(primary_key=True, serialize=False)),
|
|
81
|
+
('name', models.CharField(help_text='Name of this subscriber', max_length=255)),
|
|
82
|
+
('description', models.TextField(blank=True)),
|
|
83
|
+
('target_url', models.CharField(help_text='The base URL for webhook delivery', max_length=500)),
|
|
84
|
+
('secret', models.CharField(default=django_webhook_subscriber.utils.generate_secret, help_text='Secret key for webhook authentication via X-Secret header', max_length=64)),
|
|
85
|
+
('serializer_class', models.CharField(blank=True, help_text="Dot path to DRF serializer class (e.g., 'myapp.serializers.MySerializer')", max_length=512, validators=[django_webhook_subscriber.validators.validate_class_path])),
|
|
86
|
+
('headers', models.JSONField(blank=True, default=dict, help_text='Additional headers to send (JSON format)', validators=[django_webhook_subscriber.validators.validate_headers])),
|
|
87
|
+
('max_retries', models.PositiveIntegerField(default=3, help_text='Max delivery attempts')),
|
|
88
|
+
('retry_delay', models.PositiveIntegerField(default=60, help_text='Seconds between retries')),
|
|
89
|
+
('timeout', models.PositiveIntegerField(default=30, help_text='Request timeout in seconds')),
|
|
90
|
+
('auto_disable_after_failures', models.PositiveIntegerField(default=10, help_text='Auto-disable after N consecutive failures (0 = never)')),
|
|
91
|
+
('is_active', models.BooleanField(default=True)),
|
|
92
|
+
('consecutive_failures', models.PositiveIntegerField(default=0)),
|
|
93
|
+
('last_success', models.DateTimeField(blank=True, null=True)),
|
|
94
|
+
('last_failure', models.DateTimeField(blank=True, null=True)),
|
|
95
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
96
|
+
('updated_at', models.DateTimeField(auto_now=True)),
|
|
97
|
+
('content_type', models.ForeignKey(help_text='The model this subscriber watches', on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')),
|
|
98
|
+
],
|
|
99
|
+
options={
|
|
100
|
+
'verbose_name': 'Webhook Subscriber',
|
|
101
|
+
'verbose_name_plural': 'Webhook Subscribers',
|
|
102
|
+
'db_table': 'django_webhook_subscriber_webhook_subscriber',
|
|
103
|
+
'ordering': ['-created_at'],
|
|
104
|
+
},
|
|
105
|
+
),
|
|
106
|
+
migrations.CreateModel(
|
|
107
|
+
name='WebhookSubscription',
|
|
108
|
+
fields=[
|
|
109
|
+
('id', models.AutoField(primary_key=True, serialize=False)),
|
|
110
|
+
('event_name', models.CharField(help_text="Event name (e.g., 'created', 'published', 'archived')", max_length=100)),
|
|
111
|
+
('custom_endpoint', models.CharField(blank=True, help_text='Optional custom endpoint path or full URL', max_length=512)),
|
|
112
|
+
('is_active', models.BooleanField(default=True)),
|
|
113
|
+
('keep_last_response', models.BooleanField(default=True, help_text='Whether to store the last response received')),
|
|
114
|
+
('last_response', models.TextField(blank=True, help_text='Last response received (truncated if too long)')),
|
|
115
|
+
('last_success', models.DateTimeField(blank=True, null=True)),
|
|
116
|
+
('last_failure', models.DateTimeField(blank=True, null=True)),
|
|
117
|
+
('consecutive_failures', models.PositiveIntegerField(default=0)),
|
|
118
|
+
('total_deliveries', models.PositiveIntegerField(default=0)),
|
|
119
|
+
('successful_deliveries', models.PositiveIntegerField(default=0)),
|
|
120
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
121
|
+
('updated_at', models.DateTimeField(auto_now=True)),
|
|
122
|
+
('subscriber', models.ForeignKey(limit_choices_to=models.Q(('is_active', True)), on_delete=django.db.models.deletion.CASCADE, related_name='subscriptions', to='django_webhook_subscriber.webhooksubscriber')),
|
|
123
|
+
],
|
|
124
|
+
options={
|
|
125
|
+
'verbose_name': 'Webhook Subscription',
|
|
126
|
+
'verbose_name_plural': 'Webhook Subscriptions',
|
|
127
|
+
'db_table': 'django_webhook_subscriber_subscription',
|
|
128
|
+
'ordering': ('subscriber__name', 'event_name'),
|
|
129
|
+
},
|
|
130
|
+
),
|
|
131
|
+
migrations.AddField(
|
|
132
|
+
model_name='webhookdeliverylog',
|
|
133
|
+
name='subscription',
|
|
134
|
+
field=models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, related_name='delivery_logs', to='django_webhook_subscriber.webhooksubscription'),
|
|
135
|
+
preserve_default=False,
|
|
136
|
+
),
|
|
137
|
+
migrations.AddIndex(
|
|
138
|
+
model_name='webhookdeliverylog',
|
|
139
|
+
index=models.Index(fields=['subscription', '-created_at'], name='django_webh_subscri_af8f0d_idx'),
|
|
140
|
+
),
|
|
141
|
+
migrations.AddIndex(
|
|
142
|
+
model_name='webhookdeliverylog',
|
|
143
|
+
index=models.Index(fields=['response_status'], name='django_webh_respons_52f5e1_idx'),
|
|
144
|
+
),
|
|
145
|
+
migrations.AddIndex(
|
|
146
|
+
model_name='webhookdeliverylog',
|
|
147
|
+
index=models.Index(fields=['created_at'], name='django_webh_created_bc530f_idx'),
|
|
148
|
+
),
|
|
149
|
+
migrations.AddIndex(
|
|
150
|
+
model_name='webhookdeliverylog',
|
|
151
|
+
index=models.Index(fields=['attempt_number'], name='django_webh_attempt_e33fed_idx'),
|
|
152
|
+
),
|
|
153
|
+
migrations.DeleteModel(
|
|
154
|
+
name='WebhookRegistry',
|
|
155
|
+
),
|
|
156
|
+
migrations.AddIndex(
|
|
157
|
+
model_name='webhooksubscriber',
|
|
158
|
+
index=models.Index(fields=['content_type', 'is_active'], name='django_webh_content_784d7f_idx'),
|
|
159
|
+
),
|
|
160
|
+
migrations.AddIndex(
|
|
161
|
+
model_name='webhooksubscriber',
|
|
162
|
+
index=models.Index(fields=['is_active'], name='django_webh_is_acti_0cbbec_idx'),
|
|
163
|
+
),
|
|
164
|
+
migrations.AddIndex(
|
|
165
|
+
model_name='webhooksubscriber',
|
|
166
|
+
index=models.Index(fields=['content_type'], name='django_webh_content_6d305b_idx'),
|
|
167
|
+
),
|
|
168
|
+
migrations.AlterUniqueTogether(
|
|
169
|
+
name='webhooksubscriber',
|
|
170
|
+
unique_together={('target_url', 'content_type')},
|
|
171
|
+
),
|
|
172
|
+
migrations.AddIndex(
|
|
173
|
+
model_name='webhooksubscription',
|
|
174
|
+
index=models.Index(fields=['subscriber', 'event_name'], name='django_webh_subscri_ae1285_idx'),
|
|
175
|
+
),
|
|
176
|
+
migrations.AddIndex(
|
|
177
|
+
model_name='webhooksubscription',
|
|
178
|
+
index=models.Index(fields=['is_active'], name='django_webh_is_acti_f01243_idx'),
|
|
179
|
+
),
|
|
180
|
+
migrations.AddIndex(
|
|
181
|
+
model_name='webhooksubscription',
|
|
182
|
+
index=models.Index(fields=['subscriber', 'is_active'], name='django_webh_subscri_0376a3_idx'),
|
|
183
|
+
),
|
|
184
|
+
migrations.AddIndex(
|
|
185
|
+
model_name='webhooksubscription',
|
|
186
|
+
index=models.Index(fields=['event_name', 'is_active'], name='django_webh_event_n_8184a3_idx'),
|
|
187
|
+
),
|
|
188
|
+
migrations.AlterUniqueTogether(
|
|
189
|
+
name='webhooksubscription',
|
|
190
|
+
unique_together={('subscriber', 'event_name')},
|
|
191
|
+
),
|
|
192
|
+
]
|