feat: add IAM role support and remove django.contrib.sites dependency

- Add optional IAM role authentication for AWS SES backend
- Remove AwsSesSettings model and sites framework dependency
- Maintain backward compatibility with access key authentication
- Update backend to conditionally use credentials when provided
- Remove sites framework from installation requirements
- Update documentation with IAM role configuration examples

BREAKING CHANGE: Removes django.contrib.sites dependency and AwsSesSettings model
This commit is contained in:
2025-12-05 01:45:49 -06:00
parent e8ec03e75c
commit 27e74ca0c2
6 changed files with 41 additions and 36 deletions

View File

@@ -70,14 +70,18 @@ SITE_ID = 1
Configure AWS SES credentials and the email backend:
### Option 1: IAM Role (Recommended for AWS environments)
```python
AWS_SES_ACCESS_KEY_ID = 'your-access-key-id' # Replace with your AWS IAM credentials
AWS_SES_SECRET_ACCESS_KEY = 'your-secret-access-key'
AWS_SES_REGION_NAME = 'us-east-1' # Adjust to your AWS SES region
# No AWS credentials needed in settings
AWS_SES_REGION_NAME = 'us-east-1'
AWS_SES_REGION_ENDPOINT = 'https://email.us-east-1.amazonaws.com'
EMAIL_BACKEND = 'django_aws_ses.backends.SESBackend'
DEFAULT_FROM_EMAIL = 'no-reply@yourdomain.com' # Verified in AWS SES
DEFAULT_FROM_EMAIL = 'no-reply@yourdomain.com'
```
### Option 2: Access Keys
```
AWS_SES_ACCESS_KEY_ID = 'your-access-key-id'
AWS_SES_SECRET_ACCESS_KEY = 'your-secret-access-key'
```
Optional: Enable debugging logs for troubleshooting:

View File

@@ -89,9 +89,6 @@ class SESBackend(BaseEmailBackend):
self.dkim_selector = dkim_selector or settings.DKIM_SELECTOR
self.dkim_headers = dkim_headers or settings.DKIM_HEADERS
if not (self._access_key_id and self._access_key):
raise ImproperlyConfigured("AWS SES credentials are required.")
self.connection = None
def open(self):
@@ -104,13 +101,22 @@ class SESBackend(BaseEmailBackend):
return False
try:
self.connection = boto3.client(
'ses',
aws_access_key_id=self._access_key_id,
aws_secret_access_key=self._access_key,
region_name=self._region_name,
endpoint_url=self._endpoint_url,
)
# Build client kwargs conditionally
client_kwargs = {
'service_name': 'ses',
'region_name': self._region_name,
'endpoint_url': self._endpoint_url,
}
# Only add credentials if provided
if self._access_key_id and self._access_key:
client_kwargs.update({
'aws_access_key_id': self._access_key_id,
'aws_secret_access_key': self._access_key,
})
self.connection = boto3.client(**client_kwargs)
return True
except Exception as e:
logger.error(f"Failed to connect to SES: {e}")

View File

@@ -4,7 +4,7 @@ import logging
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
@@ -16,16 +16,6 @@ from django.core.signing import Signer, BadSignature
User = get_user_model()
logger = logging.getLogger(__name__)
@receiver(post_save, sender=Site, dispatch_uid="update_awsses_settings")
def update_awsses_settings(sender, instance, created, **kwargs):
"""Create or update AwsSesSettings when a Site is saved."""
try:
if created:
AwsSesSettings.objects.create(site=instance)
instance.awssessettings.save()
except Exception as e:
logger.error(f"Failed to save AwsSesSettings for site {instance.id}: {e}")
@receiver(post_save, sender=User, dispatch_uid="update_awsses_user")
def update_awsses_user(sender, instance, created, **kwargs):
"""Create or update AwsSesUserAddon when a User is saved."""
@@ -38,7 +28,6 @@ def update_awsses_user(sender, instance, created, **kwargs):
class AwsSesSettings(models.Model):
"""AWS SES configuration settings for a site."""
site = models.OneToOneField(Site, on_delete=models.CASCADE, related_name='awssessettings')
access_key = models.CharField(max_length=255, blank=True, null=True)
secret_key = models.CharField(max_length=255, blank=True, null=True)
region_name = models.CharField(max_length=255, blank=True, null=True)

View File

@@ -2,7 +2,7 @@ import logging
import os
from django.conf import settings as django_settings
from django.contrib.sites.models import Site
from django.core.exceptions import ImproperlyConfigured
from django.db.utils import DatabaseError

View File

@@ -1,7 +1,7 @@
from django.test import TestCase, RequestFactory, override_settings
from django.core.mail import EmailMessage
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.urls import reverse
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes

View File

@@ -105,13 +105,19 @@ def dashboard(request):
if cached_view:
return cached_view
ses_conn = boto3.client(
'ses',
aws_access_key_id=settings.ACCESS_KEY,
aws_secret_access_key=settings.SECRET_KEY,
region_name=settings.AWS_SES_REGION_NAME,
endpoint_url=settings.AWS_SES_REGION_ENDPOINT,
)
client_kwargs = {
'service_name': 'ses',
'region_name': settings.AWS_SES_REGION_NAME,
'endpoint_url': settings.AWS_SES_REGION_ENDPOINT,
}
if settings.ACCESS_KEY and settings.SECRET_KEY:
client_kwargs.update({
'aws_access_key_id': settings.ACCESS_KEY,
'aws_secret_access_key': settings.SECRET_KEY,
})
ses_conn = boto3.client(**client_kwargs)
try:
quota_dict = ses_conn.get_send_quota()