Fix T407250: build renewal exclusion from pks - #1559
Merged
Conversation
Member
Author
|
Ran on staging: |
theresnotime
marked this pull request as ready for review
August 26, 2026 13:09
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fix two defects in the
user_renewal_noticemanagement command that stoppedexpiry notice emails from being sent.
no_email_listwas built as a list of querysets and then passed toexclude(pk__in=no_email_list). Django compiles that toNOT (id IN ((SELECT ...), (SELECT ...), ...))— a list of scalarsub-queries. An empty sub-query evaluates to NULL, so
id IN (NULL, ..., 51910)is NULL for every non-matching row,NOT NULLis never TRUE, and every authorization was removed from theresult set. The command now collects primary keys into a
set()instead.With exactly one renewal application Django collapses
IN (single_subquery)into a real sub-query, which works correctly. The defect needs two or more
renewal applications where at least one refers to an authorization outside
the two-week window. That is why the existing tests never caught it — each
one had zero or one renewal application.
The
partners__isnull=Falsefilter joins the partners M2M and produced onerow per partner, so a single authorization was emailed once per partner.
Added
.distinct(). This only affects a Bundle authorization that hasdate_expiresset, because model validation forbids multi-partnernon-Bundle authorizations.
user_renewal_notice_diagnosticsreceives the same two query fixes. It alsonow evaluates the old exclusion alongside the new one and prints
<-- DEFECT PRESENTwhen the two counts differ, so the defect can be confirmedor ruled out on a running instance.
Rationale
Users stopped receiving access expiry notices. On production the command
selected 22 in-window authorizations and then emailed 0 of them; on staging
it selected 106 and emailed 0. Both are explained by defect 1 above.
Defect 2 was latent only because the query returned nothing. Fixing the
exclusion without adding
.distinct()would have sent 84 emails to a singlestaging user.
Phabricator Ticket
Bug: T407250
How Has This Been Tested?
Two tests added to
TWLight.emails.tests.UserRenewalNoticeTest:test_user_renewal_notice_unrelated_renewals_do_not_suppress— sets up twoout-of-window authorizations that each have a renewal application, and
asserts the in-window user still gets an email. Confirmed this test fails
against the previous code and passes with this change.
test_user_renewal_notice_multi_partner_sends_one_email— asserts afive-partner Bundle authorization with an expiry date gets one email.
230 tests pass across
TWLight.emails,TWLight.usersandTWLight.applications.manage.py checkreports no issues. Files areblack-formatted.
Manual check on staging, using the command's own filter logic against real
data: 106 authorizations in the window, 28 renewal sub-queries of which 25
were empty, old exclusion returned 0, corrected exclusion returned 103.
Screenshots of your changes (if appropriate):
Not applicable.
user_renewal_notice_diagnosticsoutput serves as evidence:Types of changes
What types of changes does your code introduce? Add an
xin all the boxes that apply: