Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Django 5.1 & Python 3.12, and removed the django-common dep #233

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .coveragerc

This file was deleted.

42 changes: 22 additions & 20 deletions .github/workflows/django.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
name: Django CI
name: CI

on:
push:
branches: [ master ]
branches:
- master
pull_request:
branches: [ master ]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

jobs:
build:

runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.7, 3.8, 3.9]
python: [3.8, 3.12]

steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install .
- name: Install Test Dependencies
run: |
pip install -r test_requirements.txt
- name: Run Tests
run: |
python runtests.py runtests
- uses: actions/checkout@v4
- run: pipx install poetry
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: poetry check
run: poetry check
- name: poetry install
run: poetry install
- name: django-admin check
run: python run python testmanage.py check
- name: django-admin test
run: python run python testmanage.py test
46 changes: 0 additions & 46 deletions .travis.yml

This file was deleted.

1 change: 0 additions & 1 deletion AUTHORS

This file was deleted.

4 changes: 0 additions & 4 deletions MANIFEST.in

This file was deleted.

2 changes: 0 additions & 2 deletions demo/requirements.txt

This file was deleted.

19 changes: 17 additions & 2 deletions django_cron/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,17 @@
from django_cron.core import *
from django_cron.helpers import get_class, get_current_time
from .core import (
DEFAULT_LOCK_BACKEND,
DJANGO_CRON_OUTPUT_ERRORS,
BadCronJobError,
CronJobBase,
CronJobManager,
Schedule,
)

__all__ = (
"DEFAULT_LOCK_BACKEND",
"DJANGO_CRON_OUTPUT_ERRORS",
"BadCronJobError",
"CronJobBase",
"CronJobManager",
"Schedule",
)
54 changes: 30 additions & 24 deletions django_cron/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,64 @@
from django.db.models import DurationField, ExpressionWrapper, F
from django.utils.translation import gettext_lazy as _

from django_cron.models import CronJobLog, CronJobLock
from django_cron.helpers import humanize_duration
from .helpers import humanize_duration
from .models import CronJobLock, CronJobLog


class DurationFilter(admin.SimpleListFilter):
title = _('duration')
parameter_name = 'duration'
title = _("duration")
parameter_name = "duration"

def lookups(self, request, model_admin):
return (
('lte_minute', _('<= 1 minute')),
('gt_minute', _('> 1 minute')),
('gt_hour', _('> 1 hour')),
('gt_day', _('> 1 day')),
("lte_minute", _("<= 1 minute")),
("gt_minute", _("> 1 minute")),
("gt_hour", _("> 1 hour")),
("gt_day", _("> 1 day")),
)

def queryset(self, request, queryset):
if self.value() == 'lte_minute':
return queryset.filter(end_time__lte=F('start_time') + timedelta(minutes=1))
if self.value() == 'gt_minute':
return queryset.filter(end_time__gt=F('start_time') + timedelta(minutes=1))
if self.value() == 'gt_hour':
return queryset.filter(end_time__gt=F('start_time') + timedelta(hours=1))
if self.value() == 'gt_day':
return queryset.filter(end_time__gt=F('start_time') + timedelta(days=1))
if self.value() == "lte_minute":
return queryset.filter(end_time__lte=F("start_time") + timedelta(minutes=1))
if self.value() == "gt_minute":
return queryset.filter(end_time__gt=F("start_time") + timedelta(minutes=1))
if self.value() == "gt_hour":
return queryset.filter(end_time__gt=F("start_time") + timedelta(hours=1))
if self.value() == "gt_day":
return queryset.filter(end_time__gt=F("start_time") + timedelta(days=1))


class CronJobLogAdmin(admin.ModelAdmin):
class Meta:
model = CronJobLog

search_fields = ('code', 'message')
ordering = ('-start_time',)
list_display = ('code', 'start_time', 'end_time', 'humanize_duration', 'is_success')
list_filter = ('code', 'start_time', 'is_success', DurationFilter)
search_fields = ("code", "message")
ordering = ("-start_time",)
list_display = ("code", "start_time", "end_time", "humanize_duration", "is_success")
list_filter = ("code", "start_time", "is_success", DurationFilter)

def get_queryset(self, request):
return super().get_queryset(request).annotate(
duration=ExpressionWrapper(F('end_time') - F('start_time'), DurationField()),
return (
super()
.get_queryset(request)
.annotate(
duration=ExpressionWrapper(
F("end_time") - F("start_time"), DurationField()
),
)
)

def get_readonly_fields(self, request, obj=None):
if not request.user.is_superuser and obj is not None:
names = [f.name for f in CronJobLog._meta.fields if f.name != 'id']
names = [f.name for f in CronJobLog._meta.fields if f.name != "id"]
return self.readonly_fields + tuple(names)
return self.readonly_fields

def humanize_duration(self, obj):
return humanize_duration(obj.end_time - obj.start_time)

humanize_duration.short_description = _("Duration")
humanize_duration.admin_order_field = 'duration'
humanize_duration.admin_order_field = "duration"


admin.site.register(CronJobLog, CronJobLogAdmin)
Expand Down
8 changes: 4 additions & 4 deletions django_cron/backends/lock/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ def __init__(self, cron_class, silent, *args, **kwargs):
* self.silent
for you. The rest is backend-specific.
"""
self.job_name = '.'.join([cron_class.__module__, cron_class.__name__])
self.job_name = ".".join([cron_class.__module__, cron_class.__name__])
self.job_code = cron_class.code
self.parallel = getattr(cron_class, 'ALLOW_PARALLEL_RUNS', False)
self.parallel = getattr(cron_class, "ALLOW_PARALLEL_RUNS", False)
self.silent = silent

def lock(self):
Expand All @@ -41,7 +41,7 @@ def lock(self):
Here you can optionally call self.notice_lock_failed().
"""
raise NotImplementedError(
'You have to implement lock(self) method for your class'
"You have to implement lock(self) method for your class"
)

def release(self):
Expand All @@ -51,7 +51,7 @@ def release(self):
No need to return anything currently.
"""
raise NotImplementedError(
'You have to implement release(self) method for your class'
"You have to implement release(self) method for your class"
)

def lock_failed_message(self):
Expand Down
6 changes: 3 additions & 3 deletions django_cron/backends/lock/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.core.cache import caches
from django.utils import timezone

from django_cron.backends.lock.base import DjangoCronJobLock
from .base import DjangoCronJobLock


class CacheLock(DjangoCronJobLock):
Expand Down Expand Up @@ -60,9 +60,9 @@ def get_lock_name(self):
def get_cache_timeout(self, cron_class):
try:
timeout = getattr(
cron_class, 'DJANGO_CRON_LOCK_TIME', settings.DJANGO_CRON_LOCK_TIME
cron_class, "DJANGO_CRON_LOCK_TIME", settings.DJANGO_CRON_LOCK_TIME
)
except:
except Exception:
timeout = self.DEFAULT_LOCK_TIME
return timeout

Expand Down
8 changes: 5 additions & 3 deletions django_cron/backends/lock/database.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from django_cron.backends.lock.base import DjangoCronJobLock
from django_cron.models import CronJobLock
from django.db import transaction

from django_cron.models import CronJobLock

from .base import DjangoCronJobLock


class DatabaseLock(DjangoCronJobLock):
"""
Expand All @@ -11,7 +13,7 @@ class DatabaseLock(DjangoCronJobLock):

@transaction.atomic
def lock(self):
lock, created = CronJobLock.objects.get_or_create(job_name=self.job_name)
lock, _ = CronJobLock.objects.get_or_create(job_name=self.job_name)
if lock.locked:
return False
else:
Expand Down
10 changes: 5 additions & 5 deletions django_cron/backends/lock/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.conf import settings
from django.core.files import locks

from django_cron.backends.lock.base import DjangoCronJobLock
from .base import DjangoCronJobLock


class FileLock(DjangoCronJobLock):
Expand All @@ -16,7 +16,7 @@ class FileLock(DjangoCronJobLock):
def lock(self):
lock_name = self.get_lock_name()
try:
self.__lock_fd = open(lock_name, 'w+b', 1)
self.__lock_fd = open(lock_name, "w+b", 1)
locks.lock(self.__lock_fd, locks.LOCK_EX | locks.LOCK_NB)
except IOError:
return False
Expand All @@ -27,11 +27,11 @@ def release(self):
self.__lock_fd.close()

def get_lock_name(self):
default_path = '/tmp'
path = getattr(settings, 'DJANGO_CRON_LOCKFILE_PATH', default_path)
default_path = "/tmp"
path = getattr(settings, "DJANGO_CRON_LOCKFILE_PATH", default_path)
if not os.path.isdir(path):
# let it die if failed, can't run further anyway
os.makedirs(path, exist_ok=True)

filename = self.job_name + '.lock'
filename = self.job_name + ".lock"
return os.path.join(path, filename)
Loading