Add Immediate frequency option, reorder to None/Immediate/Daily/Weekly, default Daily

Subscription frequency was missing Immediate and defaulted to Weekly.
Now offers four options in logical order with Daily as the default digest.
This commit is contained in:
russell@unturf.com 2026-02-09 10:22:16 -05:00
parent 7f3b3e2fee
commit df9270b3aa
5 changed files with 32 additions and 18 deletions

View file

@ -2,12 +2,14 @@
Console script to send email digests to shop subscribers.
Usage:
digest_sender data/development.ini --frequency immediate
digest_sender data/development.ini --frequency daily
digest_sender data/development.ini --frequency weekly --dry-run
Cron setup:
0 8 * * * /path/to/env/bin/digest_sender /path/to/production.ini --frequency daily
0 8 * * 1 /path/to/env/bin/digest_sender /path/to/production.ini --frequency weekly
*/5 * * * * /path/to/env/bin/digest_sender /path/to/production.ini --frequency immediate
0 8 * * * /path/to/env/bin/digest_sender /path/to/production.ini --frequency daily
0 8 * * 1 /path/to/env/bin/digest_sender /path/to/production.ini --frequency weekly
"""
import argparse
@ -22,6 +24,7 @@ from ..models.product import Product
from ..models.shop_subscription import (
FREQUENCY_DAILY,
FREQUENCY_WEEKLY,
FREQUENCY_IMMEDIATE,
get_verified_subscriptions_for_shop,
)
from .mail import send_email
@ -37,7 +40,7 @@ def parse_args(argv):
p.add_argument("config_uri", help="Pyramid config file, e.g. development.ini")
p.add_argument(
"--frequency",
choices=["daily", "weekly"],
choices=["immediate", "daily", "weekly"],
required=True,
help="Which frequency of subscribers to process",
)
@ -95,7 +98,12 @@ def build_digest_html(shop_name, items, unsubscribe_url, get_endpoint=""):
def run(env, frequency_str, dry_run=False):
"""Process all shops and send digests."""
frequency = FREQUENCY_DAILY if frequency_str == "daily" else FREQUENCY_WEEKLY
freq_map = {
"immediate": FREQUENCY_IMMEDIATE,
"daily": FREQUENCY_DAILY,
"weekly": FREQUENCY_WEEKLY,
}
frequency = freq_map.get(frequency_str, FREQUENCY_DAILY)
request = env["request"]
dbsession = request.dbsession
settings = request.registry.settings

View file

@ -13,10 +13,12 @@ from .meta import (
FREQUENCY_DAILY = 0
FREQUENCY_WEEKLY = 1
FREQUENCY_IMMEDIATE = 2
FREQUENCY_HUMAN = {
FREQUENCY_DAILY: "Daily",
FREQUENCY_WEEKLY: "Weekly",
FREQUENCY_IMMEDIATE: "Immediate",
}
@ -27,7 +29,7 @@ class ShopSubscription(RBase, Base):
shop_id = Column(UUIDType, foreign_key("Shop", "id"), nullable=False, index=True)
user_id = Column(UUIDType, foreign_key("User", "id"), nullable=True, index=True)
email = Column(Unicode(64), nullable=False)
frequency = Column(Integer, nullable=False, default=FREQUENCY_WEEKLY)
frequency = Column(Integer, nullable=False, default=FREQUENCY_DAILY)
verified = Column(Boolean, nullable=False, default=False)
unsubscribe_token = Column(Unicode(64), nullable=False, unique=True)
verify_token = Column(Unicode(64), nullable=True)
@ -40,7 +42,7 @@ class ShopSubscription(RBase, Base):
self.email = email
self.shop_id = shop_id
self.user_id = user_id
self.frequency = FREQUENCY_WEEKLY
self.frequency = FREQUENCY_DAILY
self.disabled = False
self.unsubscribe_token = secrets.token_urlsafe(32)
self.created_timestamp = now_timestamp()
@ -56,7 +58,7 @@ class ShopSubscription(RBase, Base):
@property
def human_frequency(self):
return FREQUENCY_HUMAN.get(self.frequency, "Weekly")
return FREQUENCY_HUMAN.get(self.frequency, "Daily")
def get_subscription_for_email_and_shop(dbsession, email, shop_id):

View file

@ -111,12 +111,15 @@
<input type="radio" name="frequency" value="-1" id="freq-none" />
<label for="freq-none" class="freq-option freq-none">None</label>
<input type="radio" name="frequency" value="1" id="freq-weekly" checked />
<label for="freq-weekly" class="freq-option">Weekly</label>
<input type="radio" name="frequency" value="2" id="freq-immediate" />
<label for="freq-immediate" class="freq-option">Immediate</label>
<input type="radio" name="frequency" value="0" id="freq-daily" />
<input type="radio" name="frequency" value="0" id="freq-daily" checked />
<label for="freq-daily" class="freq-option">Daily</label>
<input type="radio" name="frequency" value="1" id="freq-weekly" />
<label for="freq-weekly" class="freq-option">Weekly</label>
<input type="submit" name="submit" class="subscribe-btn" value="Save Preferences" />
</form>

View file

@ -2548,12 +2548,12 @@ class TestShopSubscription(unittest.TestCase):
self.assertNotEqual(sub1.unsubscribe_token, sub2.unsubscribe_token)
self.assertNotEqual(sub1.verify_token, sub2.verify_token)
def test_default_frequency_weekly(self):
"""Test that default frequency is weekly."""
from ..models.shop_subscription import ShopSubscription, FREQUENCY_WEEKLY
def test_default_frequency_daily(self):
"""Test that default frequency is daily."""
from ..models.shop_subscription import ShopSubscription, FREQUENCY_DAILY
sub = ShopSubscription("test@example.com", self.shop.id)
self.assertEqual(sub.frequency, FREQUENCY_WEEKLY)
self.assertEqual(sub.frequency, FREQUENCY_DAILY)
def test_human_frequency(self):
"""Test human_frequency property."""

View file

@ -7,6 +7,7 @@ from ..models.shop_subscription import (
ShopSubscription,
FREQUENCY_DAILY,
FREQUENCY_WEEKLY,
FREQUENCY_IMMEDIATE,
get_subscription_for_email_and_shop,
get_subscription_by_unsubscribe_token,
get_subscription_by_verify_token,
@ -45,17 +46,17 @@ def subscribe(request):
)
if request.method == "POST":
frequency_str = request.params.get("frequency", "1")
frequency_str = request.params.get("frequency", "0")
try:
frequency = int(frequency_str)
except (ValueError, TypeError):
frequency = FREQUENCY_WEEKLY
frequency = FREQUENCY_DAILY
# frequency -1 means "None" — disable notifications
wants_unsubscribe = frequency == -1
if frequency not in (FREQUENCY_DAILY, FREQUENCY_WEEKLY):
frequency = FREQUENCY_WEEKLY
if frequency not in (FREQUENCY_DAILY, FREQUENCY_WEEKLY, FREQUENCY_IMMEDIATE):
frequency = FREQUENCY_DAILY
if request.user and request.user.authenticated:
# Logged-in user: one-click subscribe