Repository: mozmeao/snippets-service

Issue:
Cannot duplicate job with publish start and publish end set in the past
I get this error when I try to copy a job that has already been completed, ie it was scheduled and finished. It's not an urgent issue, but it would be nice to be able to copy a job with the targeting parameters already selected instead of having to enter the targeting again. 

Steps:

1. Go to job that has been completed
2. Click duplicate 
3. Get error

>  Please correct the error below.
> 
>     Publish start must come before publish end.

<img width="1248" alt="Screen Shot 2019-09-12 at 10 17 28 AM" src="https://user-images.githubusercontent.com/47153345/64805729-92460480-d546-11e9-9158-c8f93917a2b8.png">



Discussion:

Resolving patch:
diff --git a/snippets/base/management/commands/update_jobs.py b/snippets/base/management/commands/update_jobs.py
--- a/snippets/base/management/commands/update_jobs.py
+++ b/snippets/base/management/commands/update_jobs.py
@@ -1,5 +1,6 @@
 from datetime import datetime, timedelta
 
+from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.core.management.base import BaseCommand
 from django.db import transaction
@@ -21,8 +22,10 @@ def handle(self, *args, **options):
         # Publish Scheduled Jobs with `publish_start` before now or without
         # publish_start.
         jobs = Job.objects.filter(status=Job.SCHEDULED).filter(
-            Q(publish_start__lte=now) | Q(publish_start=None)
+            Q(publish_start__lte=now - timedelta(minutes=settings.SNIPPETS_PUBLICATION_OFFSET)) |
+            Q(publish_start=None)
         )
+
         count_published = jobs.count()
         for job in jobs:
             job.change_status(
diff --git a/snippets/base/models.py b/snippets/base/models.py
--- a/snippets/base/models.py
+++ b/snippets/base/models.py
@@ -7,7 +7,7 @@
 import uuid
 import subprocess
 from collections import namedtuple
-from datetime import datetime, timedelta
+from datetime import datetime
 from urllib.parse import urljoin, urlparse
 
 from django.conf import settings
@@ -1859,10 +1859,6 @@ def __str__(self):
     def clean(self):
         super().clean()
 
-        self.publish_start = max(
-            datetime.utcnow() + timedelta(minutes=settings.SNIPPETS_PUBLICATION_OFFSET),
-            self.publish_start or datetime.utcnow())
-
         if ((all([self.publish_start, self.publish_end]) and
              self.publish_start >= self.publish_end)):
             raise ValidationError('Publish start must come before publish end.')


Test patch:
diff --git a/snippets/base/tests/test_commands.py b/snippets/base/tests/test_commands.py
--- a/snippets/base/tests/test_commands.py
+++ b/snippets/base/tests/test_commands.py
@@ -236,6 +236,7 @@ def test_no_data_fetched_from_one_source(self):
 
 
 class UpdateJobsTests(TestCase):
+    @override_settings(SNIPPETS_PUBLICATION_OFFSET=5)
     def test_base(self):
         now = datetime.utcnow()
         job_without_end_date = JobFactory(
@@ -249,9 +250,12 @@ def test_base(self):
             limit_impressions=10000,
             metric_last_update=now,
             publish_end=now + timedelta(days=1))
-        job_scheduled_ready_to_go = JobFactory(
+        job_scheduled_not_ready_to_go = JobFactory(
             status=models.Job.SCHEDULED,
             publish_start=now)
+        job_scheduled_ready_to_go = JobFactory(
+            status=models.Job.SCHEDULED,
+            publish_start=now - timedelta(minutes=10))
         job_scheduled_in_the_future = JobFactory(
             status=models.Job.SCHEDULED,
             publish_start=now + timedelta(days=1))
@@ -326,6 +330,7 @@ def test_base(self):
         job_without_end_date.refresh_from_db()
         job_that_has_ended.refresh_from_db()
         job_ending_in_the_future.refresh_from_db()
+        job_scheduled_not_ready_to_go.refresh_from_db()
         job_scheduled_ready_to_go.refresh_from_db()
         job_scheduled_in_the_future.refresh_from_db()
         job_cancelled.refresh_from_db()
@@ -344,6 +349,7 @@ def test_base(self):
         self.assertEqual(job_without_end_date.status, models.Job.PUBLISHED)
         self.assertEqual(job_that_has_ended.status, models.Job.COMPLETED)
         self.assertEqual(job_ending_in_the_future.status, models.Job.PUBLISHED)
+        self.assertEqual(job_scheduled_not_ready_to_go.status, models.Job.SCHEDULED)
         self.assertEqual(job_scheduled_ready_to_go.status, models.Job.PUBLISHED)
         self.assertEqual(job_scheduled_in_the_future.status, models.Job.SCHEDULED)
         self.assertEqual(job_cancelled.status, models.Job.CANCELED)
diff --git a/snippets/base/tests/test_models.py b/snippets/base/tests/test_models.py
--- a/snippets/base/tests/test_models.py
+++ b/snippets/base/tests/test_models.py
@@ -721,31 +721,6 @@ def test_clean(self):
         self.assertRaisesMessage(
             ValidationError, 'Publish start must come before publish end.', job_dirty.clean)
 
-    @override_settings(SNIPPETS_PUBLICATION_OFFSET=5)
-    def test_clean_publication_offset(self):
-        utcnow = datetime.utcnow()
-
-        job_no_publish_start = JobFactory.create(publish_start=None, publish_end=None)
-        with patch('snippets.base.models.datetime') as datetime_mock:
-            datetime_mock.utcnow.return_value = utcnow
-            job_no_publish_start.clean()
-        self.assertEqual(job_no_publish_start.publish_start, utcnow + timedelta(minutes=5))
-
-        job_publish_start_distant_future = JobFactory.create(
-            publish_start=utcnow + timedelta(days=5), publish_end=None)
-        with patch('snippets.base.models.datetime') as datetime_mock:
-            datetime_mock.utcnow.return_value = utcnow
-            job_publish_start_distant_future.clean()
-        self.assertEqual(job_publish_start_distant_future.publish_start, utcnow + timedelta(days=5))
-
-        job_publish_start_now = JobFactory.create(
-            publish_start=utcnow, publish_end=None)
-
-        with patch('snippets.base.models.datetime') as datetime_mock:
-            datetime_mock.utcnow.return_value = utcnow
-            job_publish_start_now.clean()
-        self.assertEqual(job_publish_start_now.publish_start, utcnow + timedelta(minutes=5))
-
     def test_render(self):
         self.maxDiff = None
         job = JobFactory.create(