diff --git a/awx/main/migrations/0060_v350_update_schedule_uniqueness_constraint.py b/awx/main/migrations/0060_v350_update_schedule_uniqueness_constraint.py new file mode 100644 index 0000000000..991fc6f56c --- /dev/null +++ b/awx/main/migrations/0060_v350_update_schedule_uniqueness_constraint.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.16 on 2019-02-13 17:45 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('main', '0059_v350_remove_adhoc_limit'), + ] + + operations = [ + migrations.AlterField( + model_name='schedule', + name='name', + field=models.CharField(max_length=512), + ), + migrations.AlterUniqueTogether( + name='schedule', + unique_together=set([('unified_job_template', 'name')]), + ), + ] diff --git a/awx/main/models/schedules.py b/awx/main/models/schedules.py index 4de3c7ea71..d78b78cb41 100644 --- a/awx/main/models/schedules.py +++ b/awx/main/models/schedules.py @@ -18,7 +18,7 @@ from django.utils.translation import ugettext_lazy as _ # AWX from awx.api.versioning import reverse -from awx.main.models.base import CommonModel +from awx.main.models.base import PrimordialModel from awx.main.models.jobs import LaunchTimeConfig from awx.main.utils import ignore_inventory_computed_fields from awx.main.consumers import emit_channel_notification @@ -61,11 +61,12 @@ class ScheduleManager(ScheduleFilterMethods, models.Manager): return ScheduleQuerySet(self.model, using=self._db) -class Schedule(CommonModel, LaunchTimeConfig): +class Schedule(PrimordialModel, LaunchTimeConfig): class Meta: app_label = 'main' ordering = ['-next_run'] + unique_together = ('unified_job_template', 'name') objects = ScheduleManager() @@ -74,6 +75,9 @@ class Schedule(CommonModel, LaunchTimeConfig): related_name='schedules', on_delete=models.CASCADE, ) + name = models.CharField( + max_length=512, + ) enabled = models.BooleanField( default=True, help_text=_("Enables processing of this schedule.") diff --git a/awx/main/tests/functional/models/test_schedule.py b/awx/main/tests/functional/models/test_schedule.py index 1ccd748f92..9d76789470 100644 --- a/awx/main/tests/functional/models/test_schedule.py +++ b/awx/main/tests/functional/models/test_schedule.py @@ -1,6 +1,7 @@ from datetime import datetime from django.utils.timezone import now +from django.db.utils import IntegrityError from unittest import mock import pytest import pytz @@ -292,3 +293,46 @@ def test_empty_until_property(job_template): ) s.save() assert s.until == '' + + +@pytest.mark.django_db +def test_duplicate_name_across_templates(job_template): + # Assert that duplicate name is allowed for different unified job templates. + rrule = 'DTSTART;TZID=America/New_York:20380601T120000 RRULE:FREQ=HOURLY;INTERVAL=1' + job_template_2 = JobTemplate.objects.create(name='test-job_template_2') + s1 = Schedule( + name='Some Schedule', + rrule=rrule, + unified_job_template=job_template + ) + s2 = Schedule( + name='Some Schedule', + rrule=rrule, + unified_job_template=job_template_2 + ) + s1.save() + s2.save() + + assert s1.name == s2.name + + +@pytest.mark.django_db +def test_duplicate_name_within_template(job_template): + # Assert that duplicate name is not allowed for the same unified job templates. + rrule = 'DTSTART;TZID=America/New_York:20380601T120000 RRULE:FREQ=HOURLY;INTERVAL=1' + s1 = Schedule( + name='Some Schedule', + rrule=rrule, + unified_job_template=job_template + ) + s2 = Schedule( + name='Some Schedule', + rrule=rrule, + unified_job_template=job_template + ) + + s1.save() + with pytest.raises(IntegrityError) as ierror: + s2.save() + + assert str(ierror.value) == "columns unified_job_template_id, name are not unique"