diff --git a/scipost_django/mailing_lists/admin.py b/scipost_django/mailing_lists/admin.py index 869cb63f00746339dc914cac744a09038508f870..b83dc198c3c5a126770045d0f5df423d7ee22c48 100644 --- a/scipost_django/mailing_lists/admin.py +++ b/scipost_django/mailing_lists/admin.py @@ -4,7 +4,7 @@ __license__ = "AGPL v3" from django.contrib import admin -from .models import MailchimpList +from .models import * @admin.register(MailchimpList) @@ -16,3 +16,19 @@ class MailchimpListAdmin(admin.ModelAdmin): return False +@admin.register(MailingList) +class MailingListAdmin(admin.ModelAdmin): + list_display = ["__str__", "is_opt_in", "eligible_count", "subscribed_count"] + list_filter = ["is_opt_in"] + autocomplete_fields = ["eligible_subscribers", "subscribed"] + + readonly_fields = ["_email_list"] + + def eligible_count(self, obj): + return obj.eligible_subscribers.count() + + def subscribed_count(self, obj): + return obj.subscribed.count() + + def _email_list(self, obj): + return ", ".join(obj.email_list) diff --git a/scipost_django/mailing_lists/migrations/0003_mailinglist.py b/scipost_django/mailing_lists/migrations/0003_mailinglist.py new file mode 100644 index 0000000000000000000000000000000000000000..3a5dca347fb774b8ffa1e22a1b4d0ced18c268e6 --- /dev/null +++ b/scipost_django/mailing_lists/migrations/0003_mailinglist.py @@ -0,0 +1,49 @@ +# Generated by Django 4.2.10 on 2024-04-17 10:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ( + "scipost", + "0041_alter_remark_contributor_alter_remark_recommendation_and_more", + ), + ("mailing_lists", "0002_auto_20171229_1435"), + ] + + operations = [ + migrations.CreateModel( + name="MailingList", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ("slug", models.SlugField(max_length=255, unique=True)), + ("is_opt_in", models.BooleanField(default=False)), + ( + "eligible_subscribers", + models.ManyToManyField( + blank=True, + related_name="eligible_mailing_lists", + to="scipost.contributor", + ), + ), + ( + "subscribed", + models.ManyToManyField( + blank=True, + related_name="subscribed_mailing_lists", + to="scipost.contributor", + ), + ), + ], + ), + ] diff --git a/scipost_django/mailing_lists/models.py b/scipost_django/mailing_lists/models.py index 21fd08aeb60a794460070eed6d2e2cdc956854e5..f5ffbef84e392e097a73ca835dbdb35005ccc5b5 100644 --- a/scipost_django/mailing_lists/models.py +++ b/scipost_django/mailing_lists/models.py @@ -20,7 +20,7 @@ from .constants import ( ) from .managers import MailListManager -from profiles.models import Profile +from profiles.models import Profile, ProfileEmail from scipost.behaviors import TimeStampedModel from scipost.constants import NORMAL_CONTRIBUTOR from scipost.models import Contributor @@ -160,3 +160,58 @@ class MailchimpSubscription(TimeStampedModel): "active_list", "contributor", ) + + +class MailingList(models.Model): + name = models.CharField(max_length=255) + slug = models.SlugField(max_length=255, unique=True) + + is_opt_in = models.BooleanField(default=False) + eligible_subscribers = models.ManyToManyField( + Contributor, + blank=True, + related_name="eligible_mailing_lists", + ) + subscribed = models.ManyToManyField( + Contributor, + blank=True, + related_name="subscribed_mailing_lists", + ) + + @property + def email_list(self): + """ + Returns a list of email addresses of all eligible subscribers who should receive emails from this list. + This is calculated as the set of eligible subscribers minus the set of unsubscribed subscribers. + """ + return list( + self.subscribed.annotate( + primary_email=models.Subquery( + ProfileEmail.objects.filter( + profile=models.OuterRef("profile"), primary=True + ).values("email")[:1] + ) + ).values_list("primary_email", flat=True) + ) + + def add_eligible_subscriber(self, contributor): + """Adds the contributor to the list of eligible subscribers.""" + self.eligible_subscribers.add(contributor) + # If the list is not opt-in, automatically subscribe the contributor + if not self.is_opt_in: + self.subscribe(contributor) + + def subscribe(self, contributor): + """Subscribes the contributor to the list.""" + if contributor not in self.eligible_subscribers.all(): + raise ValueError("Contributor is not eligible to subscribe to this list.") + self.subscribed.add(contributor) + + def unsubscribe(self, contributor): + """Unsubscribes the contributor from the list.""" + if contributor not in self.subscribed.all(): + raise ValueError("Contributor is not subscribed to this list.") + self.subscribed.remove(contributor) + + def __str__(self): + return self.name