diff --git a/django_celery_beat/admin.py b/django_celery_beat/admin.py index 794a9fd7..10d0c2b4 100644 --- a/django_celery_beat/admin.py +++ b/django_celery_beat/admin.py @@ -116,9 +116,9 @@ class PeriodicTaskAdmin(admin.ModelAdmin): model = PeriodicTask celery_app = current_app date_hierarchy = 'start_time' - list_display = ('__str__', 'enabled', 'interval', 'start_time', + list_display = ('name', 'enabled', 'crontab', 'interval', 'start_time', 'last_run_at', 'one_off') - list_filter = ['enabled', 'one_off', 'task', 'start_time', 'last_run_at'] + list_filter = ['enabled', 'one_off', 'task', 'start_time', 'last_run_at', 'crontab'] actions = ('enable_tasks', 'disable_tasks', 'toggle_tasks', 'run_tasks') search_fields = ('name',) fieldsets = ( @@ -133,7 +133,7 @@ class PeriodicTaskAdmin(admin.ModelAdmin): }), ('Arguments', { 'fields': ('args', 'kwargs'), - 'classes': ('extrapretty', 'wide', 'collapse', 'in'), + 'classes': ('extrapretty', 'wide', 'in'), }), ('Execution Options', { 'fields': ('expires', 'expire_seconds', 'queue', 'exchange', @@ -247,8 +247,19 @@ class ClockedScheduleAdmin(admin.ModelAdmin): ) +class CrontabScheduleAdmin(admin.ModelAdmin): + """Admin-interface for crontab items.""" + model = CrontabSchedule + celery_app = current_app + list_display = ( + 'name', + '__str__', + 'timezone', + ) + + admin.site.register(IntervalSchedule) -admin.site.register(CrontabSchedule) admin.site.register(SolarSchedule) +admin.site.register(CrontabSchedule, CrontabScheduleAdmin) admin.site.register(ClockedSchedule, ClockedScheduleAdmin) admin.site.register(PeriodicTask, PeriodicTaskAdmin) diff --git a/django_celery_beat/migrations/0016_crontabschedule_name.py b/django_celery_beat/migrations/0016_crontabschedule_name.py new file mode 100644 index 00000000..5d2246c6 --- /dev/null +++ b/django_celery_beat/migrations/0016_crontabschedule_name.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2021-12-17 07:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('django_celery_beat', '0015_edit_solarschedule_events_choices'), + ] + + operations = [ + migrations.AddField( + model_name='crontabschedule', + name='name', + field=models.CharField(blank=True, help_text='Short Description For This Cron Schedule', max_length=200, null=True, verbose_name='Name'), + ), + ] diff --git a/django_celery_beat/models.py b/django_celery_beat/models.py index b8bef6d1..62e2520e 100644 --- a/django_celery_beat/models.py +++ b/django_celery_beat/models.py @@ -299,6 +299,13 @@ class CrontabSchedule(models.Model): 'Timezone to Run the Cron Schedule on. Default is UTC.'), ) + name = models.CharField( + max_length=200, unique=False, + null=True, blank=True, + verbose_name=_('Name'), + help_text=_('Short Description For This Cron Schedule'), + ) + class Meta: """Table information.""" @@ -308,10 +315,12 @@ class Meta: 'day_of_week', 'hour', 'minute', 'timezone'] def __str__(self): - return '{0} {1} {2} {3} {4} (m/h/dM/MY/d) {5}'.format( + + return '{0} {1} {2} {3} {4} (m/h/dM/MY/d) {5}{6}'.format( cronexp(self.minute), cronexp(self.hour), cronexp(self.day_of_month), cronexp(self.month_of_year), - cronexp(self.day_of_week), str(self.timezone) + cronexp(self.day_of_week), str(self.timezone), + ' [{}]'.format(self.name) if self.name else '', ) @property @@ -593,9 +602,9 @@ def expires_(self): def __str__(self): fmt = '{0.name}: {{no schedule}}' if self.interval: - fmt = '{0.name}: {0.interval}' + fmt = '{0.name}' if self.crontab: - fmt = '{0.name}: {0.crontab}' + fmt = '{0.name}' if self.solar: fmt = '{0.name}: {0.solar}' if self.clocked: diff --git a/t/unit/test_models.py b/t/unit/test_models.py index 627e47b5..44405ade 100644 --- a/t/unit/test_models.py +++ b/t/unit/test_models.py @@ -83,7 +83,7 @@ def _test_duplicate_schedules(self, cls, kwargs, schedule=None): class CrontabScheduleTestCase(TestCase, TestDuplicatesMixin): - FIRST_VALID_TIMEZONE = timezone_field.\ + FIRST_VALID_TIMEZONE = timezone_field. \ TimeZoneField.default_choices[0][0].zone def test_default_timezone_without_settings_config(self): @@ -106,6 +106,30 @@ def test_duplicate_schedules(self): schedule = schedules.crontab(hour="4") self._test_duplicate_schedules(CrontabSchedule, kwargs, schedule) + def test_name_crontab(self): + """ + Test usual crontab creation with possible symbols in name. + This shouldn't be an issue, since Django form is sorting it out. + """ + valid_names = [ + "Crontab name", + "Crontab name with | and \\ / and '' ", + "Name with 123, 56, !@#$%^&*()-+=", # Any delimiters people want to use + "", # Empty string should be fine too. + ] + for name in valid_names: + kwargs = { + "minute": "*", + "hour": "4", + "day_of_week": "*", + "day_of_month": "*", + "month_of_year": "*", + "name": name + } + CrontabSchedule.objects.create(**kwargs) + named_crontab_get = CrontabSchedule.objects.get(**kwargs) + self.assertEqual(named_crontab_get.name, name) + class SolarScheduleTestCase(TestCase): EVENT_CHOICES = SolarSchedule._meta.get_field("event").choices diff --git a/t/unit/test_schedulers.py b/t/unit/test_schedulers.py index fdc575e8..ed48f55b 100644 --- a/t/unit/test_schedulers.py +++ b/t/unit/test_schedulers.py @@ -612,19 +612,33 @@ def test_CrontabSchedule_unicode(self): day_of_month='*/2', month_of_year='4,6', )) == '3 3 */2 4,6 tue (m/h/dM/MY/d) UTC' + # No spaces should be present at the end of the string in asserts above! + # Check if name is correct and have a space before. + assert str(CrontabSchedule( + minute=3, + hour=3, + day_of_week='tue', + day_of_month='*/2', + month_of_year='4,6', + name='Name' + )) == '3 3 */2 4,6 tue (m/h/dM/MY/d) UTC [Name]' def test_PeriodicTask_unicode_interval(self): p = self.create_model_interval(schedule(timedelta(seconds=10))) - assert str(p) == '{0}: every 10.0 seconds'.format(p.name) + assert str(p) == '{0}'.format(p.name) # Task name is name. + assert str(p.interval) == 'every 10.0 seconds' # But interval is human-readable too. def test_PeriodicTask_unicode_crontab(self): + """ + No longer show PeriodicTask name + Crontab name. + We have a separate column in admin section, so there is no need to duplicate this info in the name. + """ p = self.create_model_crontab(crontab( hour='4, 5', day_of_week='4, 5', )) - assert str(p) == """{0}: * 4,5 * * 4,5 (m/h/dM/MY/d) UTC""".format( - p.name - ) + assert str(p) == "{0}".format(p.name) # Task name is name. + assert str(p.crontab) == """* 4,5 * * 4,5 (m/h/dM/MY/d) UTC""" # But cron is human-readable too. def test_PeriodicTask_unicode_solar(self): p = self.create_model_solar(