Skip to content

Commit c6389ba

Browse files
fix: only send state change notifications once (ietf-tools#7953)
* feat: split state_change_event create / save * refactor: avoid double-save in rfceditor.py * feat: only send notifications on event creation * test: update test_notification_signal_receiver() * chore: update comment
1 parent 97b7195 commit c6389ba

4 files changed

Lines changed: 50 additions & 20 deletions

File tree

ietf/community/models.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,15 @@ def notify_events(sender, instance, **kwargs):
104104
if not isinstance(instance, DocEvent):
105105
return
106106

107+
if not kwargs.get("created", False):
108+
return # only notify on creation
109+
107110
if instance.doc.type_id != 'draft':
108111
return
109112

110113
if getattr(instance, "skip_community_list_notification", False):
111114
return
112-
115+
113116
# kludge alert: queuing a celery task in response to a signal can cause unexpected attempts to
114117
# start a Celery task during tests. To prevent this, don't queue a celery task if we're running
115118
# tests.

ietf/community/tests.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import ietf.community.views
1818
from ietf.group.models import Group
1919
from ietf.group.utils import setup_default_community_list_for_group
20+
from ietf.doc.factories import DocumentFactory
2021
from ietf.doc.models import State
2122
from ietf.doc.utils import add_state_change_event
2223
from ietf.person.models import Person, Email, Alias
@@ -439,39 +440,45 @@ def test_notification_signal_receiver(self, mock_notify_task):
439440
This implicitly tests that notify_events is hooked up to the post_save signal.
440441
"""
441442
# Arbitrary model that's not a DocEvent
442-
p = PersonFactory()
443+
person = PersonFactory()
443444
mock_notify_task.reset_mock() # clear any calls that resulted from the factories
444445
# be careful overriding SERVER_MODE - we do it here because the method
445446
# under test does not make this call when in "test" mode
446447
with override_settings(SERVER_MODE="not-test"):
447-
p.save()
448+
person.save()
448449
self.assertFalse(mock_notify_task.delay.called)
449450

450-
d = DocEventFactory()
451-
mock_notify_task.reset_mock() # clear any calls that resulted from the factories
451+
# build a DocEvent that is not yet persisted
452+
doc = DocumentFactory()
453+
d = DocEventFactory.build(by=person, doc=doc)
454+
# mock_notify_task.reset_mock() # clear any calls that resulted from the factories
452455
# be careful overriding SERVER_MODE - we do it here because the method
453456
# under test does not make this call when in "test" mode
454457
with override_settings(SERVER_MODE="not-test"):
455458
d.save()
456-
self.assertEqual(mock_notify_task.delay.call_count, 1)
459+
self.assertEqual(mock_notify_task.delay.call_count, 1, "notify_task should be run on creation of DocEvent")
457460
self.assertEqual(mock_notify_task.delay.call_args, mock.call(event_id = d.pk))
458461

459462
mock_notify_task.reset_mock()
463+
with override_settings(SERVER_MODE="not-test"):
464+
d.save()
465+
self.assertFalse(mock_notify_task.delay.called, "notify_task should not be run save of on existing DocEvent")
466+
467+
mock_notify_task.reset_mock()
468+
d = DocEventFactory.build(by=person, doc=doc)
460469
d.skip_community_list_notification = True
461470
# be careful overriding SERVER_MODE - we do it here because the method
462471
# under test does not make this call when in "test" mode
463472
with override_settings(SERVER_MODE="not-test"):
464473
d.save()
465-
self.assertFalse(mock_notify_task.delay.called)
474+
self.assertFalse(mock_notify_task.delay.called, "notify_task should not run when skip_community_list_notification is set")
466475

467-
del(d.skip_community_list_notification)
468-
d.doc.type_id="rfc" # not "draft"
469-
d.doc.save()
476+
d = DocEventFactory.build(by=person, doc=DocumentFactory(type_id="rfc"))
470477
# be careful overriding SERVER_MODE - we do it here because the method
471478
# under test does not make this call when in "test" mode
472479
with override_settings(SERVER_MODE="not-test"):
473480
d.save()
474-
self.assertFalse(mock_notify_task.delay.called)
481+
self.assertFalse(mock_notify_task.delay.called, "notify_task should not run on a document with type 'rfc'")
475482

476483
@mock.patch("ietf.utils.mail.send_mail_text")
477484
def test_notify_event_to_subscribers(self, mock_send_mail_text):

ietf/doc/utils.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,12 @@ def get_unicode_document_content(key, filename, codec='utf-8', errors='ignore'):
398398
def tags_suffix(tags):
399399
return ("::" + "::".join(t.name for t in tags)) if tags else ""
400400

401-
def add_state_change_event(doc, by, prev_state, new_state, prev_tags=None, new_tags=None, timestamp=None):
402-
"""Add doc event to explain that state change just happened."""
401+
402+
def new_state_change_event(doc, by, prev_state, new_state, prev_tags=None, new_tags=None, timestamp=None):
403+
"""Create unsaved doc event to explain that state change just happened
404+
405+
Returns None if no state change occurred.
406+
"""
403407
if prev_state and new_state:
404408
assert prev_state.type_id == new_state.type_id
405409

@@ -419,7 +423,22 @@ def add_state_change_event(doc, by, prev_state, new_state, prev_tags=None, new_t
419423
e.desc += " from %s" % (prev_state.name + tags_suffix(prev_tags))
420424
if timestamp:
421425
e.time = timestamp
422-
e.save()
426+
return e # not saved!
427+
428+
429+
def add_state_change_event(doc, by, prev_state, new_state, prev_tags=None, new_tags=None, timestamp=None):
430+
"""Add doc event to explain that state change just happened.
431+
432+
Returns None if no state change occurred.
433+
434+
Note: Creating a state change DocEvent will trigger notifications to be sent to people subscribed
435+
to the doc via a CommunityList on its first save(). If you need to adjust the event (say, changing
436+
its desc) before that notification is sent, use new_state_change_event() instead and save the
437+
event after making your changes.
438+
"""
439+
e = new_state_change_event(doc, by, prev_state, new_state, prev_tags, new_tags, timestamp)
440+
if e is not None:
441+
e.save()
423442
return e
424443

425444

ietf/sync/rfceditor.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from ietf.doc.models import ( Document, State, StateType, DocEvent, DocRelationshipName,
2222
DocTagName, RelatedDocument, RelatedDocHistory )
2323
from ietf.doc.expire import move_draft_files_to_archive
24-
from ietf.doc.utils import add_state_change_event, prettify_std_name, update_action_holders
24+
from ietf.doc.utils import add_state_change_event, new_state_change_event, prettify_std_name, update_action_holders
2525
from ietf.group.models import Group
2626
from ietf.ipr.models import IprDocRel
2727
from ietf.name.models import StdLevelName, StreamName
@@ -202,11 +202,14 @@ def update_drafts_from_queue(drafts):
202202
if prev_state != next_state:
203203
d.set_state(next_state)
204204

205-
e = add_state_change_event(d, system, prev_state, next_state)
205+
e = new_state_change_event(d, system, prev_state, next_state) # unsaved
206+
if e:
207+
if auth48:
208+
e.desc = re.sub(r"(<b>.*</b>)", "<a href=\"%s\">\\1</a>" % auth48, e.desc)
209+
e.save()
210+
events.append(e)
206211

207212
if auth48:
208-
e.desc = re.sub(r"(<b>.*</b>)", "<a href=\"%s\">\\1</a>" % auth48, e.desc)
209-
e.save()
210213
# Create or update the auth48 URL whether or not this is a state expected to have one.
211214
d.documenturl_set.update_or_create(
212215
tag_id='auth48', # look up existing based on this field
@@ -215,8 +218,6 @@ def update_drafts_from_queue(drafts):
215218
else:
216219
# Remove any existing auth48 URL when an update does not have one.
217220
d.documenturl_set.filter(tag_id='auth48').delete()
218-
if e:
219-
events.append(e)
220221

221222
changed.add(name)
222223

0 commit comments

Comments
 (0)