More precise handling of schedule computed fields no-ops

Do not set a next_run value for disabled schedules
Bail if no fields are changed
Do not update related template if its fields did not change

Change call pattern to schedule.update_computed_fields()
in doing so, fix bug where template does not pick up schedule
  due to schedules next_run not being saved

Handle the case (also a bug) where template was not updated
  when schedule was deleted
This commit is contained in:
AlanCoding
2019-05-20 20:47:28 -04:00
parent 9a1b205e06
commit 40f9d0b512
4 changed files with 185 additions and 29 deletions

View File

@@ -227,15 +227,23 @@ class Schedule(PrimordialModel, LaunchTimeConfig):
job_kwargs['_eager_fields'] = {'launch_type': 'scheduled', 'schedule': self}
return job_kwargs
def update_computed_fields(self):
future_rs = Schedule.rrulestr(self.rrule)
next_run_actual = future_rs.after(now())
def update_computed_fields_no_save(self):
affects_fields = ['next_run', 'dtstart', 'dtend']
starting_values = {}
for field_name in affects_fields:
starting_values[field_name] = getattr(self, field_name)
if next_run_actual is not None:
if not datetime_exists(next_run_actual):
# skip imaginary dates, like 2:30 on DST boundaries
next_run_actual = future_rs.after(next_run_actual)
next_run_actual = next_run_actual.astimezone(pytz.utc)
future_rs = Schedule.rrulestr(self.rrule)
if self.enabled:
next_run_actual = future_rs.after(now())
if next_run_actual is not None:
if not datetime_exists(next_run_actual):
# skip imaginary dates, like 2:30 on DST boundaries
next_run_actual = future_rs.after(next_run_actual)
next_run_actual = next_run_actual.astimezone(pytz.utc)
else:
next_run_actual = None
self.next_run = next_run_actual
try:
@@ -248,11 +256,38 @@ class Schedule(PrimordialModel, LaunchTimeConfig):
self.dtend = future_rs[-1].astimezone(pytz.utc)
except IndexError:
self.dtend = None
changed = any(getattr(self, field_name) != starting_values[field_name] for field_name in affects_fields)
return changed
def update_computed_fields(self):
changed = self.update_computed_fields_no_save()
if not changed:
return
emit_channel_notification('schedules-changed', dict(id=self.id, group_name='schedules'))
# Must save self here before calling unified_job_template computed fields
# in order for that method to be correct
# by adding modified to update fields, we avoid updating modified time
super(Schedule, self).save(update_fields=['next_run', 'dtstart', 'dtend', 'modified'])
with ignore_inventory_computed_fields():
self.unified_job_template.update_computed_fields()
def save(self, *args, **kwargs):
self.update_computed_fields()
self.rrule = Schedule.coerce_naive_until(self.rrule)
changed = self.update_computed_fields_no_save()
if changed and 'update_fields' in kwargs:
for field_name in ['next_run', 'dtstart', 'dtend']:
if field_name not in kwargs['update_fields']:
kwargs['update_fields'].append(field_name)
super(Schedule, self).save(*args, **kwargs)
if changed:
with ignore_inventory_computed_fields():
self.unified_job_template.update_computed_fields()
def delete(self, *args, **kwargs):
ujt = self.unified_job_template
r = super(Schedule, self).delete(*args, **kwargs)
if ujt:
with ignore_inventory_computed_fields():
ujt.update_computed_fields()
return r

View File

@@ -253,11 +253,13 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
return self.last_job_run
def update_computed_fields(self):
Schedule = self._meta.get_field('schedules').related_model
related_schedules = Schedule.objects.filter(enabled=True, unified_job_template=self, next_run__isnull=False).order_by('-next_run')
if related_schedules.exists():
self.next_schedule = related_schedules[0]
self.next_job_run = related_schedules[0].next_run
related_schedules = self.schedules.filter(enabled=True, next_run__isnull=False).order_by('-next_run')
new_next_schedule = related_schedules.first()
if new_next_schedule:
if new_next_schedule.pk == self.next_schedule_id and new_next_schedule.next_run == self.next_job_run:
return # no-op, common for infrequent schedules
self.next_schedule = new_next_schedule
self.next_job_run = new_next_schedule.next_run
self.save(update_fields=['next_schedule', 'next_job_run'])
def save(self, *args, **kwargs):