From 4303c1f5db521ce8330ce4e02604846722f8e15e Mon Sep 17 00:00:00 2001 From: warrenchen Date: Tue, 28 Apr 2026 02:26:29 +0900 Subject: [PATCH] feat(newsletter): Add permission to send newsletter campaigns and update model options --- .../0009_alter_newslettercampaign_options.py | 20 ++++++++++++ innovedus_cms/base/models.py | 3 ++ innovedus_cms/base/views.py | 10 ++++-- innovedus_cms/base/wagtail_hooks.py | 4 ++- innovedus_cms/mysite/settings/base.py | 32 +++++++++++++------ 5 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 innovedus_cms/base/migrations/0009_alter_newslettercampaign_options.py diff --git a/innovedus_cms/base/migrations/0009_alter_newslettercampaign_options.py b/innovedus_cms/base/migrations/0009_alter_newslettercampaign_options.py new file mode 100644 index 0000000..b97e016 --- /dev/null +++ b/innovedus_cms/base/migrations/0009_alter_newslettercampaign_options.py @@ -0,0 +1,20 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("base", "0008_alter_contactformsubmission_options_and_more"), + ] + + operations = [ + migrations.AlterModelOptions( + name="newslettercampaign", + options={ + "ordering": ["-created_at"], + "permissions": [("send_newslettercampaign", "Can send newsletter campaign")], + "verbose_name": "Newsletter Campaign", + "verbose_name_plural": "Newsletter Campaigns", + }, + ), + ] diff --git a/innovedus_cms/base/models.py b/innovedus_cms/base/models.py index e2ff281..9ef9c87 100644 --- a/innovedus_cms/base/models.py +++ b/innovedus_cms/base/models.py @@ -459,6 +459,9 @@ class NewsletterCampaign(models.Model): ordering = ["-created_at"] verbose_name = _("Newsletter Campaign") verbose_name_plural = _("Newsletter Campaigns") + permissions = [ + ("send_newslettercampaign", "Can send newsletter campaign"), + ] def __str__(self): return self.title diff --git a/innovedus_cms/base/views.py b/innovedus_cms/base/views.py index 18729e3..867f288 100644 --- a/innovedus_cms/base/views.py +++ b/innovedus_cms/base/views.py @@ -5,7 +5,8 @@ import json from django.contrib import messages from django.contrib.admin.views.decorators import staff_member_required -from django.core.exceptions import ValidationError +from django.contrib.auth.decorators import login_required +from django.core.exceptions import PermissionDenied, ValidationError from django.core.validators import validate_email from django.http import HttpResponseNotAllowed, JsonResponse from django.shortcuts import get_object_or_404, redirect, render @@ -35,6 +36,8 @@ from .newsletter_scheduler import dispatch_campaign logger = logging.getLogger(__name__) +SEND_NEWSLETTER_PERMISSION = "base.send_newslettercampaign" + @require_GET def health_check(request): @@ -464,9 +467,12 @@ def one_click_unsubscribe(request): ) -@staff_member_required +@login_required @require_GET def newsletter_campaign_send_now(request, campaign_id: int): + if not request.user.has_perm(SEND_NEWSLETTER_PERMISSION): + raise PermissionDenied + campaign = get_object_or_404(NewsletterCampaign, pk=campaign_id) if campaign.status == NewsletterCampaign.STATUS_SENDING: messages.error(request, "Campaign is currently sending.") diff --git a/innovedus_cms/base/wagtail_hooks.py b/innovedus_cms/base/wagtail_hooks.py index ea610b7..86df348 100644 --- a/innovedus_cms/base/wagtail_hooks.py +++ b/innovedus_cms/base/wagtail_hooks.py @@ -10,6 +10,8 @@ from wagtail.snippets.views.snippets import CreateView, SnippetViewSet from .models import NewsletterCampaign, NewsletterDispatchRecord, NewsletterSystemSettings +SEND_NEWSLETTER_PERMISSION = "base.send_newslettercampaign" + class NewsletterCampaignCreateView(CreateView): def get_initial(self): @@ -89,7 +91,7 @@ register_snippet(NewsletterDispatchRecordViewSet) def newsletter_campaign_listing_buttons(snippet, user, next_url=None): if not isinstance(snippet, NewsletterCampaign): return - if not user.is_staff: + if not user.has_perm(SEND_NEWSLETTER_PERMISSION): return if snippet.status == NewsletterCampaign.STATUS_SENDING: return diff --git a/innovedus_cms/mysite/settings/base.py b/innovedus_cms/mysite/settings/base.py index a7d563c..e0d60d7 100644 --- a/innovedus_cms/mysite/settings/base.py +++ b/innovedus_cms/mysite/settings/base.py @@ -55,6 +55,28 @@ def env_optional(name, default=None): return normalized +def build_media_storage_options(): + options = { + "access_key": os.environ.get("AWS_ACCESS_KEY_ID"), + "secret_key": os.environ.get("AWS_SECRET_ACCESS_KEY"), + "bucket_name": os.environ.get("AWS_STORAGE_BUCKET_NAME"), + "region_name": os.environ.get("AWS_S3_REGION_NAME", default="us-east-1"), + "default_acl": env_optional("AWS_S3_DEFAULT_ACL"), + "querystring_auth": env_bool("AWS_S3_QUERYSTRING_AUTH", default=True), + "custom_domain": env_optional("AWS_S3_CUSTOM_DOMAIN"), + } + + endpoint_url = env_optional("AWS_S3_ENDPOINT_URL") + if endpoint_url: + options["endpoint_url"] = endpoint_url + + addressing_style = env_optional("AWS_S3_ADDRESSING_STYLE") + if addressing_style: + options["addressing_style"] = addressing_style + + return options + + def detect_private_ip(): """ Return the primary private IPv4 address for this container when available. @@ -258,15 +280,7 @@ MEDIA_URL = ( STORAGES = { "default": { "BACKEND": "storages.backends.s3boto3.S3Boto3Storage", - "OPTIONS": { - "access_key": os.environ.get("AWS_ACCESS_KEY_ID"), - "secret_key": os.environ.get("AWS_SECRET_ACCESS_KEY"), - "bucket_name": os.environ.get("AWS_STORAGE_BUCKET_NAME"), - "region_name": os.environ.get("AWS_S3_REGION_NAME", default="us-east-1"), - "default_acl": env_optional("AWS_S3_DEFAULT_ACL"), - "querystring_auth": env_bool("AWS_S3_QUERYSTRING_AUTH", default=True), - "custom_domain": env_optional("AWS_S3_CUSTOM_DOMAIN"), - }, + "OPTIONS": build_media_storage_options(), }, "staticfiles": { "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",