mirror of
https://github.com/django-q2/django-q2.git
synced 2026-09-16 05:57:53 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08baaab125 | ||
|
|
ce81059da8 | ||
|
|
cf33275d92 | ||
|
|
e78e473be3 | ||
|
|
4d454e4001 | ||
|
|
cf33891291 | ||
|
|
134a54dbeb | ||
|
|
c1d4858f62 | ||
|
|
2566955782 | ||
|
|
3c0c32758d | ||
|
|
b73fea49b5 | ||
|
|
cf458304cc | ||
|
|
06ad6154e3 | ||
|
|
542e04db54 | ||
|
|
17205549eb |
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,2 @@
|
||||
# flake8, black, isort
|
||||
b1d000d007f3f77069719523268a0c6256dc0860
|
||||
37
.github/release-drafter.yml
vendored
37
.github/release-drafter.yml
vendored
@@ -1,37 +0,0 @@
|
||||
categories:
|
||||
-
|
||||
label: breaking
|
||||
title: Breaking
|
||||
-
|
||||
label: feature
|
||||
title: New
|
||||
-
|
||||
label: bug
|
||||
title: "Bug Fixes"
|
||||
-
|
||||
label: dependencies
|
||||
title: "Dependency Updates"
|
||||
-
|
||||
label: security
|
||||
title: Security
|
||||
name-template: v$NEXT_PATCH_VERSION
|
||||
tag-template: v$NEXT_PATCH_VERSION
|
||||
template: |
|
||||
# Changes
|
||||
$CHANGES
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- breaking
|
||||
- major
|
||||
minor:
|
||||
labels:
|
||||
- feature
|
||||
- minor
|
||||
patch:
|
||||
labels:
|
||||
- bug
|
||||
- dependencies
|
||||
- security
|
||||
- patch
|
||||
default: patch
|
||||
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -22,8 +22,11 @@ jobs:
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install -U pip setuptools poetry
|
||||
python -m pip install poetry
|
||||
sudo apt-get update
|
||||
sudo apt-get -y install gettext
|
||||
python -m pip install pip setuptools django poetry
|
||||
# compile messages to get .mo files
|
||||
django-admin compilemessages
|
||||
- name: Build and publish package
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
run: poetry --build --username=__token__ --password=${{ secrets.PYPI_TOKEN }} publish
|
||||
|
||||
14
.github/workflows/release_log.yml
vendored
14
.github/workflows/release_log.yml
vendored
@@ -1,14 +0,0 @@
|
||||
name: Update release draft
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
update_release_draft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
with:
|
||||
config-name: release-drafter.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -73,7 +73,11 @@ jobs:
|
||||
- name: Upload to coveralls
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install coveralls
|
||||
python -m pip install coveralls flake8 black
|
||||
coveralls --service=github --finish
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Check flake8/black
|
||||
run: |
|
||||
flake8 .
|
||||
black --check .
|
||||
|
||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -2,6 +2,39 @@
|
||||
|
||||
## [Unreleased](https://github.com/GDay/django-q2/tree/HEAD)
|
||||
|
||||
## [v1.4.8](https://github.com/GDay/django-q2/tree/v1.4.8) (2022-12-21)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix: allow both ZoneInfo and Pytz depending on django version https://github.com/GDay/django-q2/pull/55
|
||||
|
||||
## [v1.4.7](https://github.com/GDay/django-q2/tree/v1.4.7) (2022-12-21)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix: handling exceptions inside job function https://github.com/GDay/django-q2/pull/51
|
||||
- Fix: Daylight saving time issue with scheduler https://github.com/GDay/django-q2/pull/47
|
||||
- Chore: Fix badge and add download badge https://github.com/GDay/django-q2/pull/52
|
||||
- Chore: Remove release drafter https://github.com/GDay/django-q2/pull/53
|
||||
|
||||
## [v1.4.6](https://github.com/GDay/django-q2/tree/v1.4.6) (2022-11-30)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix: Log exceptions with logger.exception https://github.com/GDay/django-q2/pull/42
|
||||
- Chore: flake8, isort, black https://github.com/GDay/django-q2/pull/40
|
||||
|
||||
## [v1.4.5](https://github.com/GDay/django-q2/tree/v1.4.5) (2022-11-13)
|
||||
|
||||
- Fix release workflow
|
||||
|
||||
## [v1.4.4](https://github.com/GDay/django-q2/tree/v1.4.4) (2022-11-13)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix: Deprecation warning for Django 5.x https://github.com/GDay/django-q2/pull/34
|
||||
- Feat: Add biweekly and bimonthly https://github.com/GDay/django-q2/pull/36
|
||||
- Fix: Fix all translation strings and remove compiled https://github.com/GDay/django-q2/pull/36
|
||||
|
||||
## [v1.4.3](https://github.com/GDay/django-q2/tree/v1.4.3) (2022-11-07)
|
||||
|
||||
@@ -10,7 +43,6 @@
|
||||
- Fix: func reference in admin https://github.com/GDay/django-q2/pull/28
|
||||
- Add python 3.11 support and remove 3.7 (as it was never supported by this package anyway)
|
||||
|
||||
|
||||
## [v1.4.2](https://github.com/GDay/django-q2/tree/v1.4.2) (2022-10-22)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
A multiprocessing distributed task queue for Django
|
||||
---------------------------------------------------
|
||||
|
||||
|image0| |image1| |docs|
|
||||
|image0| |image1| |docs| |downloads|
|
||||
|
||||
::
|
||||
|
||||
@@ -245,4 +245,6 @@ Acknowledgements
|
||||
.. |docs| image:: https://readthedocs.org/projects/docs/badge/?version=latest
|
||||
:alt: Documentation Status
|
||||
:scale: 100
|
||||
:target: https://django-q.readthedocs.org/
|
||||
:target: https://django-q2.readthedocs.org/
|
||||
.. |downloads| image:: https://img.shields.io/pypi/dm/django-q2
|
||||
:target: https://img.shields.io/pypi/dm/django-q2
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
VERSION = (1, 3, 9)
|
||||
|
||||
import django
|
||||
|
||||
VERSION = (1, 4, 8)
|
||||
|
||||
if django.VERSION < (3, 2):
|
||||
default_app_config = "django_q.apps.DjangoQConfig"
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"""Admin module for Django."""
|
||||
from django.contrib import admin
|
||||
from django.db.models.expressions import OuterRef, Subquery
|
||||
from django.urls import reverse
|
||||
from django.utils.html import format_html
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.db.models.expressions import OuterRef, Subquery
|
||||
|
||||
from django_q.conf import Conf, croniter
|
||||
from django_q.models import Failure, OrmQ, Schedule, Success, Task
|
||||
@@ -82,18 +82,27 @@ class ScheduleAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ("cron",)
|
||||
|
||||
list_filter = ("next_run", "schedule_type", "cluster")
|
||||
search_fields = ("name", "func",)
|
||||
search_fields = (
|
||||
"name",
|
||||
"func",
|
||||
)
|
||||
list_display_links = ("id", "name")
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super().get_queryset(request)
|
||||
task_query = Task.objects.filter(id=OuterRef('task')).values('id', 'name', 'success')
|
||||
qs = qs.annotate(task_id=Subquery(task_query.values('id')), task_name=Subquery(task_query.values('name')),
|
||||
task_success=Subquery(task_query.values('success')))
|
||||
task_query = Task.objects.filter(id=OuterRef("task")).values(
|
||||
"id", "name", "success"
|
||||
)
|
||||
qs = qs.annotate(
|
||||
task_id=Subquery(task_query.values("id")),
|
||||
task_name=Subquery(task_query.values("name")),
|
||||
task_success=Subquery(task_query.values("success")),
|
||||
)
|
||||
return qs
|
||||
|
||||
def get_success(self, obj):
|
||||
return obj.task_success
|
||||
|
||||
get_success.boolean = True
|
||||
get_success.short_description = _("success")
|
||||
|
||||
@@ -105,6 +114,7 @@ class ScheduleAdmin(admin.ModelAdmin):
|
||||
url = reverse("admin:django_q_failure_change", args=(obj.task_id,))
|
||||
return format_html(f'<a href="{url}">[{obj.task_name}]</a>')
|
||||
return None
|
||||
|
||||
get_last_run.allow_tags = True
|
||||
get_last_run.short_description = _("last_run")
|
||||
|
||||
@@ -112,7 +122,7 @@ class ScheduleAdmin(admin.ModelAdmin):
|
||||
class QueueAdmin(admin.ModelAdmin):
|
||||
"""queue admin for ORM broker"""
|
||||
|
||||
list_display = ("id", "key", "name", "group", "func", "lock", "task_id")
|
||||
list_display = ("id", "key", "name", "group", "func", "lock", "task_id")
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
obj.save(using=Conf.ORM)
|
||||
|
||||
@@ -9,4 +9,4 @@ class DjangoQConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.AutoField"
|
||||
|
||||
def ready(self):
|
||||
from django_q.signals import call_hook
|
||||
from django_q.signals import call_hook # noqa: F401
|
||||
|
||||
@@ -39,7 +39,8 @@ class Sqs(Broker):
|
||||
raise ValueError("receive_message_wait_time_seconds should be int")
|
||||
if wait_time_second > 20:
|
||||
raise ValueError(
|
||||
"receive_message_wait_time_seconds is invalid. Reason: Must be >= 0 and <= 20"
|
||||
"receive_message_wait_time_seconds is invalid. Reason: Must be >= 0"
|
||||
" and <= 20"
|
||||
)
|
||||
params.update({"WaitTimeSeconds": wait_time_second})
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class ORM(Broker):
|
||||
|
||||
def dequeue(self):
|
||||
tasks = self.get_connection().filter(key=self.list_key, lock__lt=_timeout())[
|
||||
0 : Conf.BULK
|
||||
0 : Conf.BULK # noqa: E203
|
||||
]
|
||||
if tasks:
|
||||
task_list = []
|
||||
@@ -73,7 +73,8 @@ class ORM(Broker):
|
||||
.update(lock=timezone.now())
|
||||
):
|
||||
task_list.append((task.pk, task.payload))
|
||||
# else don't process, as another cluster has been faster than us on that task
|
||||
# else don't process, as another cluster has been faster than us on
|
||||
# that task
|
||||
return task_list
|
||||
# empty queue, spare the cpu
|
||||
sleep(Conf.POLL)
|
||||
|
||||
@@ -43,7 +43,7 @@ from django_q.signals import post_execute, pre_execute
|
||||
from django_q.signing import BadSignature, SignedPackage
|
||||
from django_q.status import Stat, Status
|
||||
|
||||
from .utils import add_months, add_years, get_func_repr
|
||||
from .utils import get_func_repr, localtime
|
||||
|
||||
|
||||
class Cluster:
|
||||
@@ -74,7 +74,7 @@ class Cluster:
|
||||
),
|
||||
)
|
||||
self.sentinel.start()
|
||||
logger.info(_(f"Q Cluster {self.name} starting."))
|
||||
logger.info(_("Q Cluster %(name)s starting.") % {"name": self.name})
|
||||
while not self.start_event.is_set():
|
||||
sleep(0.1)
|
||||
return self.pid
|
||||
@@ -82,19 +82,21 @@ class Cluster:
|
||||
def stop(self) -> bool:
|
||||
if not self.sentinel.is_alive():
|
||||
return False
|
||||
logger.info(_(f"Q Cluster {self.name} stopping."))
|
||||
logger.info(_("Q Cluster %(name)s stopping.") % {"name": self.name})
|
||||
self.stop_event.set()
|
||||
self.sentinel.join()
|
||||
logger.info(_(f"Q Cluster {self.name} has stopped."))
|
||||
logger.info(_("Q Cluster %(name)s has stopped.") % {"name": self.name})
|
||||
self.start_event = None
|
||||
self.stop_event = None
|
||||
return True
|
||||
|
||||
def sig_handler(self, signum, frame):
|
||||
logger.debug(
|
||||
_(
|
||||
f'{current_process().name} got signal {Conf.SIGNAL_NAMES.get(signum, "UNKNOWN")}'
|
||||
)
|
||||
_("%(name)s got signal %(signal)s")
|
||||
% {
|
||||
"name": current_process().name,
|
||||
"signal": Conf.SIGNAL_NAMES.get(signum, "UNKNOWN"),
|
||||
}
|
||||
)
|
||||
self.stop()
|
||||
|
||||
@@ -216,21 +218,34 @@ class Sentinel:
|
||||
db.connections.close_all()
|
||||
if process == self.monitor:
|
||||
self.monitor = self.spawn_monitor()
|
||||
logger.error(_(f"reincarnated monitor {process.name} after sudden death"))
|
||||
logger.error(
|
||||
_("reincarnated monitor %(name)s after sudden death")
|
||||
% {"name": process.name}
|
||||
)
|
||||
elif process == self.pusher:
|
||||
self.pusher = self.spawn_pusher()
|
||||
logger.error(_(f"reincarnated pusher {process.name} after sudden death"))
|
||||
logger.error(
|
||||
_("reincarnated pusher %(name)s after sudden death")
|
||||
% {"name": process.name}
|
||||
)
|
||||
else:
|
||||
self.pool.remove(process)
|
||||
self.spawn_worker()
|
||||
if process.timer.value == 0:
|
||||
# only need to terminate on timeout, otherwise we risk destabilizing the queues
|
||||
# only need to terminate on timeout, otherwise we risk destabilizing
|
||||
# the queues
|
||||
process.terminate()
|
||||
logger.warning(_(f"reincarnated worker {process.name} after timeout"))
|
||||
logger.warning(
|
||||
_("reincarnated worker %(name)s after timeout")
|
||||
% {"name": process.name}
|
||||
)
|
||||
elif int(process.timer.value) == -2:
|
||||
logger.info(_(f"recycled worker {process.name}"))
|
||||
logger.info(_("recycled worker %(name)s") % {"name": process.name})
|
||||
else:
|
||||
logger.error(_(f"reincarnated worker {process.name} after death"))
|
||||
logger.error(
|
||||
_("reincarnated worker %(name)s after death")
|
||||
% {"name": process.name}
|
||||
)
|
||||
|
||||
self.reincarnations += 1
|
||||
|
||||
@@ -252,13 +267,18 @@ class Sentinel:
|
||||
|
||||
def guard(self):
|
||||
logger.info(
|
||||
_(
|
||||
f"{current_process().name} guarding cluster {humanize(self.cluster_id.hex)}"
|
||||
)
|
||||
_("%(name)s guarding cluster %(cluster_name)s")
|
||||
% {
|
||||
"name": current_process().name,
|
||||
"cluster_name": humanize(self.cluster_id.hex),
|
||||
}
|
||||
)
|
||||
self.start_event.set()
|
||||
Stat(self).save()
|
||||
logger.info(_(f"Q Cluster {humanize(self.cluster_id.hex)} running."))
|
||||
logger.info(
|
||||
_("Q Cluster %(cluster_name)s running.")
|
||||
% {"cluster_name": humanize(self.cluster_id.hex)}
|
||||
)
|
||||
counter = 0
|
||||
cycle = Conf.GUARD_CYCLE # guard loop sleep in seconds
|
||||
# Guard loop. Runs at least once
|
||||
@@ -292,7 +312,7 @@ class Sentinel:
|
||||
def stop(self):
|
||||
Stat(self).save()
|
||||
name = current_process().name
|
||||
logger.info(_(f"{name} stopping cluster processes"))
|
||||
logger.info(_("%(name)s stopping cluster processes") % {"name": name})
|
||||
# Stopping pusher
|
||||
self.event_out.set()
|
||||
# Wait for it to stop
|
||||
@@ -317,7 +337,7 @@ class Sentinel:
|
||||
self.result_queue.close()
|
||||
# Wait for the result queue to empty
|
||||
self.result_queue.join_thread()
|
||||
logger.info(_(f"{name} waiting for the monitor."))
|
||||
logger.info(_("%(name)s waiting for the monitor.") % {"name": name})
|
||||
# Wait for everything to close or time out
|
||||
count = 0
|
||||
if not self.timeout:
|
||||
@@ -339,12 +359,15 @@ def pusher(task_queue: Queue, event: Event, broker: Broker = None):
|
||||
"""
|
||||
if not broker:
|
||||
broker = get_broker()
|
||||
logger.info(_(f"{current_process().name} pushing tasks at {current_process().pid}"))
|
||||
logger.info(
|
||||
_("%(process_name)s pushing tasks at %(id)s")
|
||||
% {"process_name": current_process().name, "id": current_process().pid}
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
task_set = broker.dequeue()
|
||||
except Exception as e:
|
||||
logger.error(e, traceback.format_exc())
|
||||
logger.exception("Failed to pull task from broker")
|
||||
# broker probably crashed. Let the sentinel handle it.
|
||||
sleep(10)
|
||||
break
|
||||
@@ -355,15 +378,17 @@ def pusher(task_queue: Queue, event: Event, broker: Broker = None):
|
||||
try:
|
||||
task = SignedPackage.loads(task[1])
|
||||
except (TypeError, BadSignature) as e:
|
||||
logger.error(e, traceback.format_exc())
|
||||
logger.exception("Failed to push task to queue")
|
||||
broker.fail(ack_id)
|
||||
continue
|
||||
task["ack_id"] = ack_id
|
||||
task_queue.put(task)
|
||||
logger.debug(_(f"queueing from {broker.list_key}"))
|
||||
logger.debug(
|
||||
_("queueing from %(list_key)s") % {"list_key": broker.list_key}
|
||||
)
|
||||
if event.is_set():
|
||||
break
|
||||
logger.info(_(f"{current_process().name} stopped pushing tasks"))
|
||||
logger.info(_("%(name)s stopped pushing tasks") % {"name": current_process().name})
|
||||
|
||||
|
||||
def monitor(result_queue: Queue, broker: Broker = None):
|
||||
@@ -375,7 +400,9 @@ def monitor(result_queue: Queue, broker: Broker = None):
|
||||
if not broker:
|
||||
broker = get_broker()
|
||||
name = current_process().name
|
||||
logger.info(_(f"{name} monitoring at {current_process().pid}"))
|
||||
logger.info(
|
||||
_("%(name)s monitoring at %(id)s") % {"name": name, "id": current_process().pid}
|
||||
)
|
||||
for task in iter(result_queue.get, "STOP"):
|
||||
# save the result
|
||||
if task.get("cached", False):
|
||||
@@ -389,28 +416,42 @@ def monitor(result_queue: Queue, broker: Broker = None):
|
||||
# signal execution done
|
||||
post_execute.send(sender="django_q", task=task)
|
||||
# log the result
|
||||
info_name = get_func_repr(task['func'])
|
||||
info_name = get_func_repr(task["func"])
|
||||
if task["success"]:
|
||||
# log success
|
||||
logger.info(_(f"Processed '{info_name}' ({task['name']})"))
|
||||
logger.info(
|
||||
_("Processed '%(info_name)s' (%(task_name)s)")
|
||||
% {"info_name": info_name, "task_name": task["name"]}
|
||||
)
|
||||
else:
|
||||
# log failure
|
||||
logger.error(_(f"Failed '{info_name}' ({task['name']}) - {task['result']}"))
|
||||
logger.info(_(f"{name} stopped monitoring results"))
|
||||
logger.error(
|
||||
_("Failed '%(info_name)s' (%(task_name)s) - %(task_result)s")
|
||||
% {
|
||||
"info_name": info_name,
|
||||
"task_name": task["name"],
|
||||
"task_result": task["result"],
|
||||
}
|
||||
)
|
||||
logger.info(_("%(name)s stopped monitoring results") % {"name": name})
|
||||
|
||||
|
||||
def worker(
|
||||
task_queue: Queue, result_queue: Queue, timer: Value, timeout: int = Conf.TIMEOUT
|
||||
):
|
||||
"""
|
||||
Takes a task from the task queue, tries to execute it and puts the result back in the result queue
|
||||
Takes a task from the task queue, tries to execute it and puts the result back in
|
||||
the result queue
|
||||
:param timeout: number of seconds wait for a worker to finish.
|
||||
:type task_queue: multiprocessing.Queue
|
||||
:type result_queue: multiprocessing.Queue
|
||||
:type timer: multiprocessing.Value
|
||||
"""
|
||||
proc_name = current_process().name
|
||||
logger.info(_(f"{proc_name} ready for work at {current_process().pid}"))
|
||||
logger.info(
|
||||
_("%(proc_name)s ready for work at %(id)s")
|
||||
% {"proc_name": proc_name, "id": current_process().pid}
|
||||
)
|
||||
task_count = 0
|
||||
if timeout is None:
|
||||
timeout = -1
|
||||
@@ -422,7 +463,14 @@ def worker(
|
||||
# Get the function from the task
|
||||
func = task["func"]
|
||||
func_name = get_func_repr(func)
|
||||
logger.info(_(f"{proc_name} processing '{func_name}' ({task['name']})"))
|
||||
logger.info(
|
||||
_("%(proc_name)s processing '%(func_name)s' (%(task_name)s)")
|
||||
% {
|
||||
"proc_name": proc_name,
|
||||
"func_name": func_name,
|
||||
"task_name": task["name"],
|
||||
}
|
||||
)
|
||||
f = task["func"]
|
||||
# if it's not an instance try to get it from the string
|
||||
if not callable(task["func"]):
|
||||
@@ -436,12 +484,12 @@ def worker(
|
||||
try:
|
||||
res = f(*task["args"], **task["kwargs"])
|
||||
result = (res, True)
|
||||
except Exception:
|
||||
result = (f"Could not process '{func_name}'. Check the location of the function and the args/kwargs.", False)
|
||||
except Exception as e:
|
||||
result = (f"{e} : {traceback.format_exc()}", False)
|
||||
if error_reporter:
|
||||
error_reporter.report()
|
||||
if task.get("sync", False):
|
||||
raise Exception(result)
|
||||
raise
|
||||
with timer.get_lock():
|
||||
# Process result
|
||||
task["result"] = result[0]
|
||||
@@ -453,7 +501,8 @@ def worker(
|
||||
if task_count == Conf.RECYCLE or rss_check():
|
||||
timer.value = -2 # Recycled
|
||||
break
|
||||
logger.info(_(f"{proc_name} stopped doing work"))
|
||||
logger.info(_("%(proc_name)s stopped doing work") % {"proc_name": proc_name})
|
||||
|
||||
|
||||
def save_task(task, broker: Broker):
|
||||
"""
|
||||
@@ -478,16 +527,27 @@ def save_task(task, broker: Broker):
|
||||
|
||||
try:
|
||||
filters = {}
|
||||
if Conf.SAVE_LIMIT_PER and Conf.SAVE_LIMIT_PER in {"group", "name", "func"} and Conf.SAVE_LIMIT_PER in task:
|
||||
if (
|
||||
Conf.SAVE_LIMIT_PER
|
||||
and Conf.SAVE_LIMIT_PER in {"group", "name", "func"}
|
||||
and Conf.SAVE_LIMIT_PER in task
|
||||
):
|
||||
value = task[Conf.SAVE_LIMIT_PER]
|
||||
if Conf.SAVE_LIMIT_PER == "func":
|
||||
value = get_func_repr(value)
|
||||
filters[Conf.SAVE_LIMIT_PER] = value
|
||||
|
||||
database_to_use = {"using": Conf.ORM if Conf.ORM else Schedule.objects.db} if not Conf.HAS_REPLICA else {}
|
||||
database_to_use = (
|
||||
{"using": Conf.ORM if Conf.ORM else Schedule.objects.db}
|
||||
if not Conf.HAS_REPLICA
|
||||
else {}
|
||||
)
|
||||
with db.transaction.atomic(**database_to_use):
|
||||
last = Success.objects.filter(**filters).select_for_update().last()
|
||||
if task["success"] and 0 < Conf.SAVE_LIMIT <= Success.objects.filter(**filters).count():
|
||||
if (
|
||||
task["success"]
|
||||
and 0 < Conf.SAVE_LIMIT <= Success.objects.filter(**filters).count()
|
||||
):
|
||||
last.delete()
|
||||
|
||||
# check if this task has previous results
|
||||
@@ -588,7 +648,11 @@ def scheduler(broker: Broker = None):
|
||||
broker = get_broker()
|
||||
close_old_django_connections()
|
||||
try:
|
||||
database_to_use = {"using": Conf.ORM if Conf.ORM else Schedule.objects.db} if not Conf.HAS_REPLICA else {}
|
||||
database_to_use = (
|
||||
{"using": Conf.ORM if Conf.ORM else Schedule.objects.db}
|
||||
if not Conf.HAS_REPLICA
|
||||
else {}
|
||||
)
|
||||
with db.transaction.atomic(**database_to_use):
|
||||
for s in (
|
||||
Schedule.objects.select_for_update()
|
||||
@@ -608,8 +672,13 @@ def scheduler(broker: Broker = None):
|
||||
except (SyntaxError, ValueError):
|
||||
# else use the kwargs syntax
|
||||
try:
|
||||
parsed_kwargs = ast.parse(f"f({s.kwargs})").body[0].value.keywords
|
||||
kwargs = {kwarg.arg: ast.literal_eval(kwarg.value) for kwarg in parsed_kwargs}
|
||||
parsed_kwargs = (
|
||||
ast.parse(f"f({s.kwargs})").body[0].value.keywords
|
||||
)
|
||||
kwargs = {
|
||||
kwarg.arg: ast.literal_eval(kwarg.value)
|
||||
for kwarg in parsed_kwargs
|
||||
}
|
||||
except (SyntaxError, ValueError):
|
||||
kwargs = {}
|
||||
if s.args:
|
||||
@@ -624,28 +693,7 @@ def scheduler(broker: Broker = None):
|
||||
if s.schedule_type != s.ONCE:
|
||||
next_run = s.next_run
|
||||
while True:
|
||||
if s.schedule_type == s.MINUTES:
|
||||
next_run = next_run + timedelta(minutes=(s.minutes or 1))
|
||||
elif s.schedule_type == s.HOURLY:
|
||||
next_run = next_run + timedelta(hours=1)
|
||||
elif s.schedule_type == s.DAILY:
|
||||
next_run = next_run + timedelta(days=1)
|
||||
elif s.schedule_type == s.WEEKLY:
|
||||
next_run = next_run + timedelta(weeks=1)
|
||||
elif s.schedule_type == s.MONTHLY:
|
||||
next_run = add_months(next_run, 1)
|
||||
elif s.schedule_type == s.QUARTERLY:
|
||||
next_run = add_months(next_run, 3)
|
||||
elif s.schedule_type == s.YEARLY:
|
||||
next_run = add_years(next_run, 1)
|
||||
elif s.schedule_type == s.CRON:
|
||||
if not croniter:
|
||||
raise ImportError(
|
||||
_(
|
||||
"Please install croniter to enable cron expressions"
|
||||
)
|
||||
)
|
||||
next_run = croniter(s.cron, localtime()).get_next(datetime)
|
||||
next_run = s.calculate_next_run(next_run)
|
||||
if Conf.CATCH_UP or next_run > localtime():
|
||||
break
|
||||
|
||||
@@ -655,7 +703,8 @@ def scheduler(broker: Broker = None):
|
||||
scheduled_broker = broker
|
||||
try:
|
||||
scheduled_broker = get_broker(q_options["broker_name"])
|
||||
except: # invalid broker_name or non existing broker with broker_name
|
||||
except: # noqa: E722
|
||||
# invalid broker_name or non existing broker with broker_name
|
||||
pass
|
||||
q_options["broker"] = scheduled_broker
|
||||
q_options["group"] = q_options.get("group", s.name or s.id)
|
||||
@@ -665,14 +714,24 @@ def scheduler(broker: Broker = None):
|
||||
if not s.task:
|
||||
logger.error(
|
||||
_(
|
||||
f"{current_process().name} failed to create a task from schedule [{s.name or s.id}]"
|
||||
"%(process_name)s failed to create a task from schedule "
|
||||
"[%(schedule)s]"
|
||||
)
|
||||
% {
|
||||
"process_name": current_process().name,
|
||||
"schedule": s.name or s.id,
|
||||
}
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
_(
|
||||
f"{current_process().name} created a task from schedule [{s.name or s.id}]"
|
||||
"%(process_name)s created a task from schedule "
|
||||
"[%(schedule)s]"
|
||||
)
|
||||
% {
|
||||
"process_name": current_process().name,
|
||||
"schedule": s.name or s.id,
|
||||
}
|
||||
)
|
||||
# default behavior is to delete a ONCE schedule
|
||||
if s.schedule_type == s.ONCE:
|
||||
@@ -711,12 +770,12 @@ def set_cpu_affinity(n: int, process_ids: list, actual: bool = not Conf.TESTING)
|
||||
"""
|
||||
# check if we have the psutil module
|
||||
if not psutil:
|
||||
logger.warning("Skipping cpu affinity because psutil was not found.")
|
||||
logger.warning(_("Skipping cpu affinity because psutil was not found."))
|
||||
return
|
||||
# check if the platform supports cpu_affinity
|
||||
if actual and not hasattr(psutil.Process(process_ids[0]), "cpu_affinity"):
|
||||
logger.warning(
|
||||
"Faking cpu affinity because it is not supported on this platform"
|
||||
_("Faking cpu affinity because it is not supported on this platform")
|
||||
)
|
||||
actual = False
|
||||
# get the available processors
|
||||
@@ -737,7 +796,10 @@ def set_cpu_affinity(n: int, process_ids: list, actual: bool = not Conf.TESTING)
|
||||
p = psutil.Process(pid)
|
||||
if actual:
|
||||
p.cpu_affinity(affinity)
|
||||
logger.info(_(f"{pid} will use cpu {affinity}"))
|
||||
logger.info(
|
||||
_("%(pid)s will use cpu %(affinity)s")
|
||||
% {"pid": pid, "affinity": affinity}
|
||||
)
|
||||
|
||||
|
||||
def rss_check():
|
||||
@@ -747,10 +809,3 @@ def rss_check():
|
||||
elif psutil:
|
||||
return psutil.Process().memory_info().rss >= Conf.MAX_RSS * 1024
|
||||
return False
|
||||
|
||||
|
||||
def localtime() -> datetime:
|
||||
"""Override for timezone.localtime to deal with naive times and local times"""
|
||||
if settings.USE_TZ:
|
||||
return timezone.localtime()
|
||||
return datetime.now()
|
||||
|
||||
@@ -73,7 +73,8 @@ class Conf:
|
||||
# Log output level
|
||||
LOG_LEVEL = conf.get("log_level", "INFO")
|
||||
|
||||
# Maximum number of successful tasks kept in the database. 0 saves everything. -1 saves none
|
||||
# Maximum number of successful tasks kept in the database. 0 saves everything.
|
||||
# -1 saves none
|
||||
# Failures are always saved
|
||||
SAVE_LIMIT = conf.get("save_limit", 250)
|
||||
|
||||
@@ -82,7 +83,13 @@ class Conf:
|
||||
|
||||
# Verify SAVE_LIMIT_PER is valid
|
||||
if SAVE_LIMIT_PER not in ["group", "name", "func", None]:
|
||||
warn(f"SAVE_LIMIT_PER ({SAVE_LIMIT_PER}) is not a valid option. Options are: 'group', 'name', 'func' and None. Default is None.")
|
||||
warn(
|
||||
_(
|
||||
"SAVE_LIMIT_PER (%(option)s) is not a valid option. Options are: "
|
||||
"'group', 'name', 'func' and None. Default is None."
|
||||
)
|
||||
% {"option": SAVE_LIMIT_PER}
|
||||
)
|
||||
|
||||
# Guard loop sleep in seconds. Should be between 0 and 60 seconds.
|
||||
GUARD_CYCLE = conf.get("guard_cycle", 0.5)
|
||||
@@ -113,11 +120,12 @@ class Conf:
|
||||
# Sets compression of redis packages
|
||||
COMPRESSED = conf.get("compress", False)
|
||||
|
||||
# Number of tasks each worker can handle before it gets recycled. Useful for releasing memory
|
||||
# Number of tasks each worker can handle before it gets recycled.
|
||||
# Useful for releasing memory
|
||||
RECYCLE = conf.get("recycle", 500)
|
||||
|
||||
# The maximum resident set size in kilobytes before a worker will recycle. Useful for limiting memory usage
|
||||
# Not available on all platforms
|
||||
# The maximum resident set size in kilobytes before a worker will recycle.
|
||||
# Useful for limiting memory usage. Not available on all platforms
|
||||
MAX_RSS = conf.get("max_rss", None)
|
||||
|
||||
# Number of seconds to wait for a worker to finish.
|
||||
@@ -135,9 +143,10 @@ class Conf:
|
||||
# Verify if retry and timeout settings are correct
|
||||
if not TIMEOUT or (TIMEOUT > RETRY):
|
||||
warn(
|
||||
"""Retry and timeout are misconfigured. Set retry larger than timeout,
|
||||
failure to do so will cause the tasks to be retriggered before completion.
|
||||
See https://django-q2.readthedocs.io/en/master/configure.html#retry for details."""
|
||||
"Retry and timeout are misconfigured. Set retry larger than timeout,"
|
||||
"failure to do so will cause the tasks to be retriggered before completion."
|
||||
"See https://django-q2.readthedocs.io/en/master/configure.html#retry "
|
||||
"for details."
|
||||
)
|
||||
|
||||
# Sets the amount of tasks the cluster will try to pop off the broker.
|
||||
@@ -156,12 +165,14 @@ class Conf:
|
||||
# The Django cache to use
|
||||
CACHE = conf.get("cache", "default")
|
||||
|
||||
# Use the cache as result backend. Can be 'True' or an integer representing the global cache timeout.
|
||||
# Use the cache as result backend. Can be 'True' or an integer representing the
|
||||
# global cache timeout.
|
||||
# i.e 'cached: 60' , will make all results go the cache and expire in 60 seconds.
|
||||
CACHED = conf.get("cached", False)
|
||||
|
||||
# If set to False the scheduler won't execute tasks in the past.
|
||||
# Instead it will run once and reschedule the next run in the future. Defaults to True.
|
||||
# Instead it will run once and reschedule the next run in the future. Defaults to
|
||||
# True.
|
||||
CATCH_UP = conf.get("catch_up", True)
|
||||
|
||||
# Use the secret key for package signing
|
||||
@@ -200,6 +211,11 @@ class Conf:
|
||||
# to manage workarounds during testing
|
||||
TESTING = conf.get("testing", False)
|
||||
|
||||
# Timezone for next_run, overrules Django timezone
|
||||
TIME_ZONE = None
|
||||
if settings.USE_TZ:
|
||||
TIME_ZONE = conf.get("time_zone", settings.TIME_ZONE)
|
||||
|
||||
|
||||
# logger
|
||||
logger = logging.getLogger("django-q")
|
||||
@@ -257,5 +273,6 @@ def get_ppid():
|
||||
return psutil.Process(os.getpid()).ppid()
|
||||
else:
|
||||
raise OSError(
|
||||
"Your OS does not support `os.getppid`. Please install `psutil` as an alternative provider."
|
||||
"Your OS does not support `os.getppid`. Please install `psutil` as an "
|
||||
"alternative provider."
|
||||
)
|
||||
|
||||
@@ -6,7 +6,12 @@ from django.core.signing import BadSignature, JSONSerializer, SignatureExpired
|
||||
from django.core.signing import Signer as Sgnr
|
||||
from django.core.signing import TimestampSigner as TsS
|
||||
from django.core.signing import b64_decode, dumps
|
||||
from django.utils import baseconv
|
||||
|
||||
try:
|
||||
from django.core.signing import base62
|
||||
except ImportError:
|
||||
# For django < 4.0
|
||||
from django.utils.baseconv import base62
|
||||
from django.utils.crypto import constant_time_compare
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
|
||||
@@ -69,7 +74,7 @@ class TimestampSigner(Signer, TsS):
|
||||
"""
|
||||
result = super(TimestampSigner, self).unsign(value)
|
||||
value, timestamp = result.rsplit(self.sep, 1)
|
||||
timestamp = baseconv.base62.decode(timestamp)
|
||||
timestamp = base62.decode(timestamp)
|
||||
if max_age is not None:
|
||||
if isinstance(max_age, datetime.timedelta):
|
||||
max_age = max_age.total_seconds()
|
||||
|
||||
@@ -337,12 +337,18 @@ class HumanHasher:
|
||||
|
||||
# Split `bytes` into `target` segments.
|
||||
seg_size = length // target
|
||||
segments = [bytes[i * seg_size : (i + 1) * seg_size] for i in range(target)]
|
||||
# fmt: off
|
||||
segments = [
|
||||
bytes[i * seg_size : (i + 1) * seg_size] for i in range(target) # noqa: E203 E501
|
||||
]
|
||||
# fmt: on
|
||||
# Catch any left-over bytes in the last segment.
|
||||
segments[-1].extend(bytes[target * seg_size :])
|
||||
segments[-1].extend(bytes[target * seg_size :]) # noqa: E203 E501
|
||||
|
||||
# Use a simple XOR checksum-like function for compression.
|
||||
checksum = lambda bytes: reduce(operator.xor, bytes, 0)
|
||||
def checksum(bytes):
|
||||
return reduce(operator.xor, bytes, 0)
|
||||
|
||||
checksums = list(map(checksum, segments))
|
||||
return checksums
|
||||
|
||||
|
||||
Binary file not shown.
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-01-15 15:53+0100\n"
|
||||
"POT-Creation-Date: 2022-11-12 01:47+0000\n"
|
||||
"PO-Revision-Date: 2018-08-05 18:28+0200\n"
|
||||
"Last-Translator: Jonas Winkler\n"
|
||||
"Language-Team: \n"
|
||||
@@ -17,431 +17,471 @@ msgstr ""
|
||||
"X-Generator: Poedit 2.1.1\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
#: admin.py:40
|
||||
#: django_q/admin.py:43
|
||||
msgid "Resubmit selected tasks to queue"
|
||||
msgstr "Ausgewählte Aufgaben erneut ausführen"
|
||||
|
||||
#: brokers/disque.py:59
|
||||
msgid "No Disque nodes configured"
|
||||
msgstr "Keine Disque-Knoten konfiguriert"
|
||||
#: django_q/admin.py:98 django_q/models.py:228
|
||||
#, fuzzy
|
||||
#| msgid "Success"
|
||||
msgid "success"
|
||||
msgstr "erfolg"
|
||||
|
||||
#: brokers/disque.py:76
|
||||
msgid "Could not connect to any Disque nodes"
|
||||
msgstr "Konnte zu keinem Disque-Knoten verbinden"
|
||||
#: django_q/admin.py:109 django_q/models.py:230
|
||||
msgid "last_run"
|
||||
msgstr ""
|
||||
|
||||
#: cluster.py:76
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Q Cluster-{} starting."
|
||||
msgid "Q Cluster {self.name} starting."
|
||||
msgstr "Q-Cluster {self.name} wird gestartet."
|
||||
#: django_q/cluster.py:77
|
||||
#, python-format
|
||||
msgid "Q Cluster %(name)s starting."
|
||||
msgstr "Q-Cluster %(name)s wird gestartet."
|
||||
|
||||
#: cluster.py:84
|
||||
#, fuzzy, python-brace-format
|
||||
#: django_q/cluster.py:85
|
||||
#, fuzzy, python-format
|
||||
#| msgid "Q Cluster-{} stopping."
|
||||
msgid "Q Cluster {self.name} stopping."
|
||||
msgstr "Q-Cluster {self.name} wird gestoppt."
|
||||
msgid "Q Cluster %(name)s stopping."
|
||||
msgstr "Q-Cluster {name} wird gestoppt."
|
||||
|
||||
#: cluster.py:87
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Q Cluster-{} has stopped."
|
||||
msgid "Q Cluster {self.name} has stopped."
|
||||
msgstr "Q-Cluster {self.name} wurde gestoppt."
|
||||
#: django_q/cluster.py:88
|
||||
#, python-format
|
||||
msgid "Q Cluster %(name)s has stopped."
|
||||
msgstr "Q-Cluster %(name)s wurde gestoppt."
|
||||
|
||||
#: cluster.py:95
|
||||
#: django_q/cluster.py:96
|
||||
#, python-format
|
||||
msgid "%(name)s got signal %(signal)s"
|
||||
msgstr "%(name)s erhielt das Signal %(signal)s"
|
||||
|
||||
#: django_q/cluster.py:219
|
||||
#, python-format
|
||||
msgid "reincarnated monitor %(name)s after sudden death"
|
||||
msgstr "Monitor %(name)s wurde nach unerwartetem Absturz neu gestartet"
|
||||
|
||||
#: django_q/cluster.py:222
|
||||
#, python-format
|
||||
msgid "reincarnated pusher %(name)s after sudden death"
|
||||
msgstr "Pusher %(name)s wurde nach unerwartetem Absturz neu gestartet"
|
||||
|
||||
#: django_q/cluster.py:229
|
||||
#, python-format
|
||||
msgid "reincarnated worker %(name)s after timeout"
|
||||
msgstr "Worker %(name)s wurde nach Zeitüberschreitung neu gestartet"
|
||||
|
||||
#: django_q/cluster.py:231
|
||||
#, python-format
|
||||
msgid "recycled worker %(name)s"
|
||||
msgstr "Worker %(name)s wurde wiederverwendet"
|
||||
|
||||
#: django_q/cluster.py:233
|
||||
#, python-format
|
||||
msgid "reincarnated worker %(name)s after death"
|
||||
msgstr "Worker %(name)s wurde nach unerwartetem Absturz neu gestartet"
|
||||
|
||||
#: django_q/cluster.py:256
|
||||
#, python-format
|
||||
msgid "%(name)s guarding cluster %(cluster_name)s"
|
||||
msgstr "%(name)s beschützt das Cluster %(cluster_name)s"
|
||||
|
||||
#: django_q/cluster.py:261
|
||||
#, python-format
|
||||
msgid "Q Cluster %(cluster_name)s running."
|
||||
msgstr "Q-Cluster %(cluster_name)s läuft."
|
||||
|
||||
#: django_q/cluster.py:295
|
||||
#, python-format
|
||||
msgid "%(name)s stopping cluster processes"
|
||||
msgstr "%(name)s hält Cluster-Prozesse an"
|
||||
|
||||
#: django_q/cluster.py:320
|
||||
#, python-format
|
||||
msgid "%(name)s waiting for the monitor."
|
||||
msgstr "%(name)s wartet auf den Monitor."
|
||||
|
||||
#: django_q/cluster.py:342
|
||||
#, python-format
|
||||
msgid "%(process_name)s pushing tasks at %(id)s"
|
||||
msgstr "%(process_name)s veröffentlicht Aufagaben auf %(id)s"
|
||||
|
||||
#: django_q/cluster.py:363
|
||||
#, python-format
|
||||
msgid "queueing from %(list_key)s"
|
||||
msgstr "Einreihen von %(list_key)s"
|
||||
|
||||
#: django_q/cluster.py:366
|
||||
#, python-format
|
||||
msgid "%(name)s stopped pushing tasks"
|
||||
msgstr "%(name)s veröffentlicht keine Aufgaben mehr"
|
||||
|
||||
#: django_q/cluster.py:378
|
||||
#, python-format
|
||||
msgid "%(name)s monitoring at %(id)s"
|
||||
msgstr "%(name)s beobachtet auf %(id)s"
|
||||
|
||||
#: django_q/cluster.py:395
|
||||
#, python-format
|
||||
msgid "Processed '%(info_name)s' (%(task_name)s)"
|
||||
msgstr "[%(task_name)s] - '%(info_name)s' wurde verarbeitet"
|
||||
|
||||
#: django_q/cluster.py:398
|
||||
#, python-format
|
||||
msgid "Failed '%(info_name)s' (%(task_name)s) - %(task_result)s"
|
||||
msgstr "'%(info_name)s' (%(task_name)s) ist fehlgeschlagen - %(task_result)s"
|
||||
|
||||
#: django_q/cluster.py:399
|
||||
#, python-format
|
||||
msgid "%(name)s stopped monitoring results"
|
||||
msgstr "%(name)s überwacht keine Ergebnisse mehr"
|
||||
|
||||
#: django_q/cluster.py:413
|
||||
#, python-format
|
||||
msgid "%(proc_name)s ready for work at %(id)s"
|
||||
msgstr "%(proc_name)s ist bereit für Arbeit auf %(id)s"
|
||||
|
||||
#: django_q/cluster.py:425
|
||||
#, python-format
|
||||
msgid "%(proc_name)s processing '%(func_name)s' (%(task_name)s)"
|
||||
msgstr "%(proc_name)s verarbeitet '%(func_name)s' (%(task_name)s)"
|
||||
|
||||
#: django_q/cluster.py:440
|
||||
#, python-format
|
||||
msgid ""
|
||||
"{current_process().name} got signal {Conf.SIGNAL_NAMES.get(signum, \"UNKNOWN"
|
||||
"\")}"
|
||||
"Could not process '%(func_name)s'. Check the location of the function and "
|
||||
"the args/kwargs."
|
||||
msgstr ""
|
||||
"{current_process().name} erhielt das Signal {Conf.SIGNAL_NAMES.get(signum, "
|
||||
"\"UNKNOWN\")}"
|
||||
"Konnte '%(func_name)s' nicht verarbeiten. Überprüfen Sie den Ort der "
|
||||
"Funktion und die args/kwargs."
|
||||
|
||||
#: cluster.py:216
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "reincarnated monitor {} after sudden death"
|
||||
msgid "reincarnated monitor {process.name} after sudden death"
|
||||
msgstr "Monitor {process.name} wurde nach unerwartetem Absturz neu gestartet"
|
||||
#: django_q/cluster.py:456
|
||||
#, python-format
|
||||
msgid "%(proc_name)s stopped doing work"
|
||||
msgstr "%(proc_name)s hat die Arbeit beendet"
|
||||
|
||||
#: cluster.py:219
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "reincarnated pusher {} after sudden death"
|
||||
msgid "reincarnated pusher {process.name} after sudden death"
|
||||
msgstr "Pusher {process.name} wurde nach unerwartetem Absturz neu gestartet"
|
||||
|
||||
#: cluster.py:226
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "reincarnated worker {} after timeout"
|
||||
msgid "reincarnated worker {process.name} after timeout"
|
||||
msgstr "Worker {process.name} wurde nach Zeitüberschreitung neu gestartet"
|
||||
|
||||
#: cluster.py:228
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "recycled worker {}"
|
||||
msgid "recycled worker {process.name}"
|
||||
msgstr "Worker {process.name} wurde wiederverwendet"
|
||||
|
||||
#: cluster.py:230
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "reincarnated worker {} after death"
|
||||
msgid "reincarnated worker {process.name} after death"
|
||||
msgstr "Worker {process.name} wurde nach unerwartetem Absturz neu gestartet"
|
||||
|
||||
#: cluster.py:251
|
||||
msgid ""
|
||||
"{current_process().name} guarding cluster {humanize(self.cluster_id.hex)}"
|
||||
msgstr ""
|
||||
"{current_process().name} beschützt das Cluster {humanize(self.cluster_id."
|
||||
"hex)}"
|
||||
|
||||
#: cluster.py:256
|
||||
msgid "Q Cluster {humanize(self.cluster_id.hex)} running."
|
||||
msgstr "Q-Cluster {humanize(self.cluster_id.hex)} ist bereit."
|
||||
|
||||
#: cluster.py:290
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "{} stopping cluster processes"
|
||||
msgid "{name} stopping cluster processes"
|
||||
msgstr "{name} hält Cluster-Prozesse an"
|
||||
|
||||
#: cluster.py:315
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "{} waiting for the monitor."
|
||||
msgid "{name} waiting for the monitor."
|
||||
msgstr "{name} wartet auf den Monitor."
|
||||
|
||||
#: cluster.py:337
|
||||
msgid "{current_process().name} pushing tasks at {current_process().pid}"
|
||||
msgstr ""
|
||||
"{current_process().name} veröffentlicht Aufagaben auf {current_process().pid}"
|
||||
|
||||
#: cluster.py:358
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "queueing from {}"
|
||||
msgid "queueing from {broker.list_key}"
|
||||
msgstr "Einreihen von {broker.list_key}"
|
||||
|
||||
#: cluster.py:361
|
||||
#, fuzzy
|
||||
#| msgid "{} stopped pushing tasks"
|
||||
msgid "{current_process().name} stopped pushing tasks"
|
||||
msgstr "{current_process().name} veröffentlicht keine Aufgaben mehr"
|
||||
|
||||
#: cluster.py:373
|
||||
#, fuzzy
|
||||
#| msgid "{} monitoring at {}"
|
||||
msgid "{name} monitoring at {current_process().pid}"
|
||||
msgstr "{name} beobachtet auf {current_process().pid}"
|
||||
|
||||
#: cluster.py:387
|
||||
#, fuzzy
|
||||
#| msgid "Processed [{}]"
|
||||
msgid "Processed [{task['name']}]"
|
||||
msgstr "[{task['name']}] wurde verarbeitet"
|
||||
|
||||
#: cluster.py:390
|
||||
msgid "Failed [{task['name']}] - {task['result']}"
|
||||
msgstr "[{task['name']}] ist fehlgeschlagen - {task['result']}"
|
||||
|
||||
#: cluster.py:391
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "{} stopped monitoring results"
|
||||
msgid "{name} stopped monitoring results"
|
||||
msgstr "{name} überwacht keine Ergebnisse mehr"
|
||||
|
||||
#: cluster.py:405
|
||||
#, fuzzy
|
||||
#| msgid "{} ready for work at {}"
|
||||
msgid "{name} ready for work at {current_process().pid}"
|
||||
msgstr "{name} ist bereit für Arbeit auf {current_process().pid}"
|
||||
|
||||
#: cluster.py:415
|
||||
#, fuzzy
|
||||
#| msgid "{} processing [{}]"
|
||||
msgid "{name} processing [{task[\"name\"]}]"
|
||||
msgstr "{name} verarbeitet [{task[\"name\"]}]"
|
||||
|
||||
#: cluster.py:455
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "{} stopped doing work"
|
||||
msgid "{name} stopped doing work"
|
||||
msgstr "{name} hat die Arbeit beendet"
|
||||
|
||||
#: cluster.py:619 models.py:143
|
||||
#: django_q/cluster.py:649 django_q/models.py:144
|
||||
msgid "Please install croniter to enable cron expressions"
|
||||
msgstr "Bitte installieren Sie croniter, um Cron-Ausdrücke zu aktivieren"
|
||||
|
||||
#: cluster.py:644
|
||||
#, fuzzy
|
||||
#| msgid "{} failed to create a task from schedule [{}]"
|
||||
msgid ""
|
||||
"{current_process().name} failed to create a task from schedule [{s.name or s."
|
||||
"id}]"
|
||||
#: django_q/cluster.py:672
|
||||
#, python-format
|
||||
msgid "%(process_name)s failed to create a task from schedule [%(schedule)s]"
|
||||
msgstr ""
|
||||
"{current_process().name} konnte keine Aufgabe von Zeitplan [{s.name or s."
|
||||
"id}] erstellen"
|
||||
"%(process_name)s konnte keine Aufgabe von Zeitplan [%(schedule)s] erstellen"
|
||||
|
||||
#: cluster.py:650
|
||||
#, fuzzy
|
||||
#| msgid "{} created a task from schedule [{}]"
|
||||
msgid ""
|
||||
"{current_process().name} created a task from schedule [{s.name or s.id}]"
|
||||
#: django_q/cluster.py:678
|
||||
#, python-format
|
||||
msgid "%(process_name)s created a task from schedule [%(schedule)s]"
|
||||
msgstr ""
|
||||
"{current_process().name} hat eine Aufgabe des Zeitplans [{s.name or s.id}] "
|
||||
"erstellt"
|
||||
"%(process_name)s hat eine Aufgabe des Zeitplans [%(schedule)s] erstellt"
|
||||
|
||||
#: cluster.py:716
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "{} will use cpu {}"
|
||||
msgid "{pid} will use cpu {affinity}"
|
||||
msgstr "{pid} wird CPU {affinity} benutzen"
|
||||
#: django_q/cluster.py:718
|
||||
msgid "Skipping cpu affinity because psutil was not found."
|
||||
msgstr "Cpu-Affinität wird übersprungen, da psutil nicht gefunden wurde."
|
||||
|
||||
#: django_q/cluster.py:723
|
||||
msgid "Faking cpu affinity because it is not supported on this platform"
|
||||
msgstr ""
|
||||
"Vortäuschen von CPU-Affinität, da diese auf dieser Plattform nicht "
|
||||
"unterstützt wird"
|
||||
|
||||
#: django_q/cluster.py:744
|
||||
#, python-format
|
||||
msgid "%(pid)s will use cpu %(affinity)s"
|
||||
msgstr "%(pid)s wird CPU %(affinity)s benutzen"
|
||||
|
||||
#: django_q/conf.py:85
|
||||
#, python-format
|
||||
msgid ""
|
||||
"SAVE_LIMIT_PER (%(option)s) is not a valid option. Options are: 'group', "
|
||||
"'name', 'func' and None. Default is None."
|
||||
msgstr ""
|
||||
"SAVE_LIMIT_PER (%(option)s) ist keine gültige Option. Optionen sind: "
|
||||
"'group', 'name', 'func' und None. Standard ist None."
|
||||
|
||||
#. Translators: Cluster status descriptions
|
||||
#: conf.py:184
|
||||
#: django_q/conf.py:194
|
||||
msgid "Starting"
|
||||
msgstr "Wird gestartet"
|
||||
|
||||
#: conf.py:185
|
||||
#: django_q/conf.py:195
|
||||
msgid "Working"
|
||||
msgstr "Arbeitet"
|
||||
|
||||
#: conf.py:186
|
||||
#: django_q/conf.py:196
|
||||
msgid "Idle"
|
||||
msgstr "Leerlauf"
|
||||
|
||||
#: conf.py:187
|
||||
#: django_q/conf.py:197
|
||||
msgid "Stopped"
|
||||
msgstr "Gestoppt"
|
||||
|
||||
#: conf.py:188
|
||||
#: django_q/conf.py:198
|
||||
msgid "Stopping"
|
||||
msgstr "Wird gestoppt"
|
||||
|
||||
#. Translators: help text for qcluster management command
|
||||
#: management/commands/qcluster.py:9
|
||||
#: django_q/management/commands/qcluster.py:9
|
||||
msgid "Starts a Django Q Cluster."
|
||||
msgstr "Startet ein Django-Q-Cluster."
|
||||
|
||||
#. Translators: help text for qinfo management command
|
||||
#: management/commands/qinfo.py:11
|
||||
#: django_q/management/commands/qinfo.py:11
|
||||
msgid "General information over all clusters."
|
||||
msgstr "Allgemeine Informationen über alle Cluster"
|
||||
|
||||
#. Translators: help text for qmemory management command
|
||||
#: django_q/management/commands/qmemory.py:9
|
||||
msgid "Monitors Q Cluster memory usage"
|
||||
msgstr "Überwacht die Speichernutzung von Q Cluster"
|
||||
|
||||
#. Translators: help text for qmonitor management command
|
||||
#: management/commands/qmonitor.py:9
|
||||
#: django_q/management/commands/qmonitor.py:9
|
||||
msgid "Monitors Q Cluster activity"
|
||||
msgstr "Q-Cluster aktiv überwachen"
|
||||
|
||||
#: models.py:118
|
||||
#: django_q/models.py:119
|
||||
msgid "Successful task"
|
||||
msgstr "Erfolgreiche Aufgabe"
|
||||
|
||||
#: models.py:119
|
||||
#: django_q/models.py:120
|
||||
msgid "Successful tasks"
|
||||
msgstr "Erfolgreiche Aufgaben"
|
||||
|
||||
#: models.py:134
|
||||
#: django_q/models.py:135
|
||||
msgid "Failed task"
|
||||
msgstr "Fehlgeschlagene Aufgabe"
|
||||
|
||||
#: models.py:135
|
||||
#: django_q/models.py:136
|
||||
msgid "Failed tasks"
|
||||
msgstr "Fehlgeschlagene Aufgaben"
|
||||
|
||||
#: models.py:159
|
||||
#: django_q/models.py:160
|
||||
msgid "e.g. 1, 2, 'John'"
|
||||
msgstr "zum Beispiel 1, 2, 'John'"
|
||||
|
||||
#: models.py:161
|
||||
#: django_q/models.py:162
|
||||
msgid "e.g. x=1, y=2, name='John'"
|
||||
msgstr "zum Beispiel x=1, y=2, name='John'"
|
||||
|
||||
#: models.py:173
|
||||
#: django_q/models.py:176
|
||||
msgid "Once"
|
||||
msgstr "Einmal"
|
||||
|
||||
#: models.py:174
|
||||
#: django_q/models.py:177
|
||||
msgid "Minutes"
|
||||
msgstr "Minuten"
|
||||
|
||||
#: models.py:175
|
||||
#: django_q/models.py:178
|
||||
msgid "Hourly"
|
||||
msgstr "Stündlich"
|
||||
|
||||
#: models.py:176
|
||||
#: django_q/models.py:179
|
||||
msgid "Daily"
|
||||
msgstr "Täglich"
|
||||
|
||||
#: models.py:177
|
||||
#: django_q/models.py:180
|
||||
msgid "Weekly"
|
||||
msgstr "Wöchentlich"
|
||||
|
||||
#: models.py:178
|
||||
#: django_q/models.py:181
|
||||
msgid "Biweekly"
|
||||
msgstr "Zweiwöchentlich"
|
||||
|
||||
#: django_q/models.py:182
|
||||
msgid "Monthly"
|
||||
msgstr "Monatlich"
|
||||
|
||||
#: models.py:179
|
||||
#: django_q/models.py:183
|
||||
msgid "Bimonthly"
|
||||
msgstr "Zweimonatlich"
|
||||
|
||||
#: django_q/models.py:184
|
||||
msgid "Quarterly"
|
||||
msgstr "Vierteljährlich"
|
||||
|
||||
#: models.py:180
|
||||
#: django_q/models.py:185
|
||||
msgid "Yearly"
|
||||
msgstr "Jährlich"
|
||||
|
||||
#: models.py:181
|
||||
#: django_q/models.py:186
|
||||
msgid "Cron"
|
||||
msgstr "Cron"
|
||||
|
||||
#: models.py:184
|
||||
#: django_q/models.py:189
|
||||
msgid "Schedule Type"
|
||||
msgstr "Zeitplan-Typ"
|
||||
|
||||
#: models.py:187
|
||||
#: django_q/models.py:192
|
||||
msgid "Number of minutes for the Minutes type"
|
||||
msgstr "Anzahl Minuten für den Typ 'Minuten'"
|
||||
|
||||
#: models.py:190
|
||||
#: django_q/models.py:195
|
||||
msgid "Repeats"
|
||||
msgstr "Wiederhohlungen"
|
||||
|
||||
#: models.py:190
|
||||
#: django_q/models.py:195
|
||||
msgid "n = n times, -1 = forever"
|
||||
msgstr "n = n mal, -1 = für immer"
|
||||
|
||||
#: models.py:193
|
||||
#: django_q/models.py:198
|
||||
msgid "Next Run"
|
||||
msgstr "Nächste Ausführung"
|
||||
|
||||
#: models.py:200
|
||||
#: django_q/models.py:205
|
||||
msgid "Cron expression"
|
||||
msgstr "Cron-Ausdruck"
|
||||
|
||||
#: models.py:226
|
||||
#: django_q/models.py:235
|
||||
msgid "Scheduled task"
|
||||
msgstr "Geplante Aufgabe"
|
||||
|
||||
#: models.py:227
|
||||
#: django_q/models.py:236
|
||||
msgid "Scheduled tasks"
|
||||
msgstr "Geplante Aufgaben"
|
||||
|
||||
#: models.py:250
|
||||
#: django_q/models.py:262
|
||||
msgid "Queued task"
|
||||
msgstr "Eingereihte Aufgabe"
|
||||
|
||||
#: models.py:251
|
||||
#: django_q/models.py:263
|
||||
msgid "Queued tasks"
|
||||
msgstr "Eingereihte Aufgaben"
|
||||
|
||||
#: monitor.py:35
|
||||
#: django_q/monitor.py:62 django_q/monitor.py:339
|
||||
msgid "Host"
|
||||
msgstr "Host"
|
||||
|
||||
#: monitor.py:39
|
||||
#: django_q/monitor.py:66 django_q/monitor.py:343 django_q/monitor.py:450
|
||||
msgid "Id"
|
||||
msgstr "Id"
|
||||
|
||||
#: monitor.py:43
|
||||
#: django_q/monitor.py:70
|
||||
msgid "State"
|
||||
msgstr "Status"
|
||||
|
||||
#: monitor.py:47
|
||||
#: django_q/monitor.py:74
|
||||
msgid "Pool"
|
||||
msgstr "Pool"
|
||||
|
||||
#: monitor.py:51
|
||||
#: django_q/monitor.py:78
|
||||
msgid "TQ"
|
||||
msgstr "TQ"
|
||||
|
||||
#: monitor.py:55
|
||||
#: django_q/monitor.py:82
|
||||
msgid "RQ"
|
||||
msgstr "RQ"
|
||||
|
||||
#: monitor.py:59
|
||||
#: django_q/monitor.py:86
|
||||
msgid "RC"
|
||||
msgstr "RC"
|
||||
|
||||
#: monitor.py:63
|
||||
#: django_q/monitor.py:90
|
||||
msgid "Up"
|
||||
msgstr "Up"
|
||||
|
||||
#: monitor.py:143 monitor.py:247
|
||||
#: django_q/monitor.py:170 django_q/monitor.py:279
|
||||
msgid "Queued"
|
||||
msgstr "Eingereiht"
|
||||
|
||||
#: monitor.py:151
|
||||
#: django_q/monitor.py:178
|
||||
msgid "Success"
|
||||
msgstr "Erfolg"
|
||||
|
||||
#: monitor.py:161 monitor.py:255
|
||||
#: django_q/monitor.py:188 django_q/monitor.py:287
|
||||
msgid "Failures"
|
||||
msgstr "Fehlschläge"
|
||||
|
||||
#: monitor.py:172
|
||||
#: django_q/monitor.py:199 django_q/monitor.py:485
|
||||
msgid "[Press q to quit]"
|
||||
msgstr "[Drücken Sie q zum Beenden]"
|
||||
|
||||
#: monitor.py:191
|
||||
#: django_q/monitor.py:223
|
||||
msgid "day"
|
||||
msgstr "Tag"
|
||||
|
||||
#: monitor.py:212
|
||||
#: django_q/monitor.py:244
|
||||
msgid "second"
|
||||
msgstr "Sekunde"
|
||||
|
||||
#: monitor.py:215
|
||||
#: django_q/monitor.py:247
|
||||
msgid "minute"
|
||||
msgstr "Minute"
|
||||
|
||||
#: monitor.py:218
|
||||
#: django_q/monitor.py:250
|
||||
msgid "hour"
|
||||
msgstr "Stunde"
|
||||
|
||||
#: monitor.py:228
|
||||
msgid ""
|
||||
"-- {Conf.PREFIX.capitalize()} { \".\".join(str(v) for v in VERSION)} on "
|
||||
"{broker.info()} --"
|
||||
msgstr ""
|
||||
"-- {Conf.PREFIX.capitalize()} { \".\".join(str(v) for v in VERSION)} auf "
|
||||
"{broker.info()} --"
|
||||
#: django_q/monitor.py:260
|
||||
#, python-format
|
||||
msgid "-- %(prefix)s %(version)s on %(info)s --"
|
||||
msgstr "-- %(prefix)s %(version)s auf %(info)s --"
|
||||
|
||||
#: monitor.py:234
|
||||
#: django_q/monitor.py:266
|
||||
msgid "Clusters"
|
||||
msgstr "Cluster"
|
||||
|
||||
#: monitor.py:238
|
||||
#: django_q/monitor.py:270
|
||||
msgid "Workers"
|
||||
msgstr "Arbeiter"
|
||||
|
||||
#: monitor.py:242
|
||||
#: django_q/monitor.py:274
|
||||
msgid "Restarts"
|
||||
msgstr "Neustarts"
|
||||
|
||||
#: monitor.py:251
|
||||
#: django_q/monitor.py:283
|
||||
msgid "Successes"
|
||||
msgstr "Erfolge"
|
||||
|
||||
#: monitor.py:260
|
||||
#: django_q/monitor.py:292
|
||||
msgid "Schedules"
|
||||
msgstr "Zeitpläne"
|
||||
|
||||
#: monitor.py:264
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Tasks/{}"
|
||||
msgid "Tasks/{per}"
|
||||
msgstr "Aufgaben/{per}"
|
||||
#: django_q/monitor.py:296
|
||||
#, python-format
|
||||
msgid "Tasks/%(per)s"
|
||||
msgstr "Aufgaben/%(per)s"
|
||||
|
||||
#: monitor.py:268
|
||||
#: django_q/monitor.py:300
|
||||
msgid "Avg time"
|
||||
msgstr "Durchschnittl. Zeit"
|
||||
|
||||
#: signals.py:22
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "malformed return hook '{}' for [{}]"
|
||||
msgid "malformed return hook '{instance.hook}' for [{instance.name}]"
|
||||
msgstr "Ungültiger Return-Hook '{instance.hook}' für [{instance.name}]"
|
||||
#: django_q/monitor.py:348
|
||||
msgid "Available (%)"
|
||||
msgstr "Verfügbar (%)"
|
||||
|
||||
#: signals.py:30
|
||||
#, fuzzy
|
||||
#| msgid "return hook {} failed on [{}] because {}"
|
||||
msgid ""
|
||||
"return hook {instance.hook} failed on [{instance.name}] because {str(e)}"
|
||||
msgstr ""
|
||||
"Return-Hook {instance.hook} für [{instance.name}] ist gescheitert: {str(e)}"
|
||||
#: django_q/monitor.py:354
|
||||
msgid "Available (MB)"
|
||||
msgstr "Verfügbar (MB)"
|
||||
|
||||
#: django_q/monitor.py:359
|
||||
msgid "Total (MB)"
|
||||
msgstr "Insgesamt (MB)"
|
||||
|
||||
#: django_q/monitor.py:364
|
||||
msgid "Sentinel (MB)"
|
||||
msgstr "Sentinel (MB)"
|
||||
|
||||
#: django_q/monitor.py:370
|
||||
msgid "Monitor (MB)"
|
||||
msgstr "Monitor (MB)"
|
||||
|
||||
#: django_q/monitor.py:376
|
||||
msgid "Workers (MB)"
|
||||
msgstr "Arbeiter (MB)"
|
||||
|
||||
#: django_q/monitor.py:478
|
||||
#, python-format
|
||||
msgid "Available lowest (): %(memory_percent)s ((at)s)"
|
||||
msgstr "Niedrigste verfügbar (): %(memory_percent)s ((at)s)"
|
||||
|
||||
#: django_q/monitor.py:496
|
||||
msgid "No clusters appear to be running."
|
||||
msgstr "Es scheinen keine Cluster zu laufen."
|
||||
|
||||
#: django_q/signals.py:22
|
||||
#, python-format
|
||||
msgid "malformed return hook '%(hook)s' for [%(name)s]"
|
||||
msgstr "Ungültiger Return-Hook '%(hook)s' für [%(name)s]"
|
||||
|
||||
#: django_q/signals.py:30
|
||||
#, python-format
|
||||
msgid "return hook %(hook)s failed on [%(name)s] because %(error)s"
|
||||
msgstr "Return-Hook %(hook)s für [%(name)s] ist gescheitert: %(error)s"
|
||||
|
||||
Binary file not shown.
@@ -6,349 +6,489 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-08-05 15:48+0200\n"
|
||||
"POT-Creation-Date: 2022-11-12 01:47+0000\n"
|
||||
"PO-Revision-Date: 2018-08-05 18:28+0200\n"
|
||||
"Last-Translator: Thierry BOULOGNE <contact@tng-concepts.com>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: fr-FR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.1.1\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"Last-Translator: Thierry BOULOGNE <contact@tng-concepts.com>\n"
|
||||
"Language: fr-FR\n"
|
||||
|
||||
#: admin.py:48
|
||||
#: django_q/admin.py:43
|
||||
msgid "Resubmit selected tasks to queue"
|
||||
msgstr "Resoumettre les tâches sélectionnées à la file d'attente"
|
||||
|
||||
#: cluster.py:54
|
||||
msgid "Q Cluster-{} starting."
|
||||
msgstr "Démarrage de Q Cluster-{}."
|
||||
#: django_q/admin.py:98 django_q/models.py:228
|
||||
#, fuzzy
|
||||
#| msgid "Success"
|
||||
msgid "success"
|
||||
msgstr "succès"
|
||||
|
||||
#: cluster.py:62
|
||||
msgid "Q Cluster-{} stopping."
|
||||
msgstr "Arrêt de Q Cluster-{}."
|
||||
#: django_q/admin.py:109 django_q/models.py:230
|
||||
msgid "last_run"
|
||||
msgstr ""
|
||||
|
||||
#: cluster.py:65
|
||||
msgid "Q Cluster-{} has stopped."
|
||||
msgstr "Q Cluster-{} a été arrêté."
|
||||
#: django_q/cluster.py:77
|
||||
#, python-format
|
||||
msgid "Q Cluster %(name)s starting."
|
||||
msgstr "Démarrage de Q Cluster-%(name)s."
|
||||
|
||||
#: cluster.py:71
|
||||
msgid "{} got signal {}"
|
||||
msgstr "{} à reçu le signal {}"
|
||||
#: django_q/cluster.py:85
|
||||
#, python-format
|
||||
msgid "Q Cluster %(name)s stopping."
|
||||
msgstr "Arrêt de Q Cluster-%(name)s."
|
||||
|
||||
#: cluster.py:169
|
||||
msgid "reincarnated monitor {} after sudden death"
|
||||
msgstr "moniteur réintégré {} après un arrêt intempestif"
|
||||
#: django_q/cluster.py:88
|
||||
#, python-format
|
||||
msgid "Q Cluster %(name)s has stopped."
|
||||
msgstr "Q Cluster-%(name)s a été arrêté."
|
||||
|
||||
#: cluster.py:172
|
||||
msgid "reincarnated pusher {} after sudden death"
|
||||
msgstr "pousseur réintégré {} après un arrêt intempestif"
|
||||
#: django_q/cluster.py:96
|
||||
#, python-format
|
||||
msgid "%(name)s got signal %(signal)s"
|
||||
msgstr "%(name)s à reçu le signal %(signal)s"
|
||||
|
||||
#: cluster.py:179
|
||||
msgid "reincarnated worker {} after timeout"
|
||||
msgstr "processus réintégré {} après un arrêt une attente trop longue"
|
||||
#: django_q/cluster.py:219
|
||||
#, python-format
|
||||
msgid "reincarnated monitor %(name)s after sudden death"
|
||||
msgstr "moniteur réintégré %(name)s après un arrêt intempestif"
|
||||
|
||||
#: cluster.py:181
|
||||
msgid "recycled worker {}"
|
||||
msgstr "processus recyclé"
|
||||
#: django_q/cluster.py:222
|
||||
#, python-format
|
||||
msgid "reincarnated pusher %(name)s after sudden death"
|
||||
msgstr "pousseur réintégré %(name)s après un arrêt intempestif"
|
||||
|
||||
#: cluster.py:183
|
||||
msgid "reincarnated worker {} after death"
|
||||
msgstr "processus réintégré {} après arrêt"
|
||||
#: django_q/cluster.py:229
|
||||
#, python-format
|
||||
msgid "reincarnated worker %(name)s after timeout"
|
||||
msgstr "processus réintégré %(name)s après un arrêt une attente trop longue"
|
||||
|
||||
#: cluster.py:202
|
||||
msgid "{} guarding cluster at {}"
|
||||
msgstr "{} surveillance du cluster à {}"
|
||||
#: django_q/cluster.py:231
|
||||
#, python-format
|
||||
msgid "recycled worker %(name)s"
|
||||
msgstr "processus recyclé %(name)s"
|
||||
|
||||
#: cluster.py:205
|
||||
msgid "Q Cluster-{} running."
|
||||
msgstr "Q Cluster-{} en cours d'exécution."
|
||||
#: django_q/cluster.py:233
|
||||
#, python-format
|
||||
msgid "reincarnated worker %(name)s after death"
|
||||
msgstr "processus réintégré %(name)s après arrêt"
|
||||
|
||||
#: cluster.py:239
|
||||
msgid "{} stopping cluster processes"
|
||||
msgstr "{} arrêt des processus de cluster"
|
||||
#: django_q/cluster.py:256
|
||||
#, python-format
|
||||
msgid "%(name)s guarding cluster %(cluster_name)s"
|
||||
msgstr "%(name)s surveillance du cluster à %(cluster_name)s"
|
||||
|
||||
#: cluster.py:264
|
||||
msgid "{} waiting for the monitor."
|
||||
msgstr "{} en attente du moniteur."
|
||||
#: django_q/cluster.py:261
|
||||
#, python-format
|
||||
msgid "Q Cluster %(cluster_name)s running."
|
||||
msgstr "Démarrage de Q Cluster-%(cluster_name)s."
|
||||
|
||||
#: cluster.py:285
|
||||
msgid "{} pushing tasks at {}"
|
||||
msgstr "{} tâche envoyé à {}"
|
||||
#: django_q/cluster.py:295
|
||||
#, python-format
|
||||
msgid "%(name)s stopping cluster processes"
|
||||
msgstr "%(name)s arrêt des processus de cluster"
|
||||
|
||||
#: cluster.py:306
|
||||
msgid "queueing from {}"
|
||||
msgstr "mise en file d'attente de {}"
|
||||
#: django_q/cluster.py:320
|
||||
#, python-format
|
||||
msgid "%(name)s waiting for the monitor."
|
||||
msgstr "%(name)s en attente du moniteur."
|
||||
|
||||
#: cluster.py:309
|
||||
msgid "{} stopped pushing tasks"
|
||||
msgstr "{} a cessé de pousser les tâches"
|
||||
#: django_q/cluster.py:342
|
||||
#, python-format
|
||||
msgid "%(process_name)s pushing tasks at %(id)s"
|
||||
msgstr "%(process_name)s tâche envoyé à %(id)s"
|
||||
|
||||
#: cluster.py:320
|
||||
msgid "{} monitoring at {}"
|
||||
msgstr "{} Surveillance de {}"
|
||||
#: django_q/cluster.py:363
|
||||
#, python-format
|
||||
msgid "queueing from %(list_key)s"
|
||||
msgstr "mise en file d'attente de %(list_key)s"
|
||||
|
||||
#: cluster.py:334
|
||||
msgid "Processed [{}]"
|
||||
msgstr "Traitement de [{}]"
|
||||
#: django_q/cluster.py:366
|
||||
#, python-format
|
||||
msgid "%(name)s stopped pushing tasks"
|
||||
msgstr "%(name)s a cessé de pousser les tâches"
|
||||
|
||||
#: cluster.py:337
|
||||
msgid "Failed [{}] - {}"
|
||||
msgstr "Echec [{}] - {}"
|
||||
#: django_q/cluster.py:378
|
||||
#, python-format
|
||||
msgid "%(name)s monitoring at %(id)s"
|
||||
msgstr "%(name)s Surveillance de %(id)s"
|
||||
|
||||
#: cluster.py:338
|
||||
msgid "{} stopped monitoring results"
|
||||
msgstr "{} arrêt des résultats de surveillance"
|
||||
#: django_q/cluster.py:395
|
||||
#, python-format
|
||||
msgid "Processed '%(info_name)s' (%(task_name)s)"
|
||||
msgstr "traité '%(info_name)s' (%(task_name)s)"
|
||||
|
||||
#: cluster.py:349
|
||||
msgid "{} ready for work at {}"
|
||||
msgstr "{} prêt pour le travail à {}"
|
||||
#: django_q/cluster.py:398
|
||||
#, python-format
|
||||
msgid "Failed '%(info_name)s' (%(task_name)s) - %(task_result)s"
|
||||
msgstr "Manqué '%(info_name)s' (%(task_name)s) - %(task_result)s"
|
||||
|
||||
#: cluster.py:357
|
||||
msgid "{} processing [{}]"
|
||||
msgstr "{} en cours de traitement [{}]"
|
||||
#: django_q/cluster.py:399
|
||||
#, python-format
|
||||
msgid "%(name)s stopped monitoring results"
|
||||
msgstr "%(name)s arrêt des résultats de surveillance"
|
||||
|
||||
#: cluster.py:398
|
||||
msgid "{} stopped doing work"
|
||||
msgstr "{} arrêté de travailler"
|
||||
#: django_q/cluster.py:413
|
||||
#, python-format
|
||||
msgid "%(proc_name)s ready for work at %(id)s"
|
||||
msgstr "%(proc_name)s prêt pour le travail à %(id)s"
|
||||
|
||||
#: cluster.py:543
|
||||
msgid "{} failed to create a task from schedule [{}]"
|
||||
msgstr "{} Echec de la création d'une tâche à partir de Schedule [{}]"
|
||||
#: django_q/cluster.py:425
|
||||
#, python-format
|
||||
msgid "%(proc_name)s processing '%(func_name)s' (%(task_name)s)"
|
||||
msgstr "%(proc_name)s en traitement '%(func_name)s' (%(task_name)s)"
|
||||
|
||||
#: cluster.py:547
|
||||
msgid "{} created a task from schedule [{}]"
|
||||
msgstr "{} a créé une tâche à partir de Schedule [{}]"
|
||||
#: django_q/cluster.py:440
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Could not process '%(func_name)s'. Check the location of the function and "
|
||||
"the args/kwargs."
|
||||
msgstr ""
|
||||
"Impossible de traiter '%(func_name)s'. Vérifiez l'emplacement de la fonction "
|
||||
"et les args/kwargs."
|
||||
|
||||
#: cluster.py:595
|
||||
msgid "{} will use cpu {}"
|
||||
msgstr "{} utilisera le CPU {}"
|
||||
#: django_q/cluster.py:456
|
||||
#, python-format
|
||||
msgid "%(proc_name)s stopped doing work"
|
||||
msgstr "%(proc_name)s arrêté de travailler"
|
||||
|
||||
#: conf.py:169
|
||||
#: django_q/cluster.py:649 django_q/models.py:144
|
||||
msgid "Please install croniter to enable cron expressions"
|
||||
msgstr "Veuillez installer croniter pour activer les expressions croniques."
|
||||
|
||||
#: django_q/cluster.py:672
|
||||
#, python-format
|
||||
msgid "%(process_name)s failed to create a task from schedule [%(schedule)s]"
|
||||
msgstr ""
|
||||
"%(process_name)s Echec de la création d'une tâche à partir de Schedule "
|
||||
"[%(schedule)s]"
|
||||
|
||||
#: django_q/cluster.py:678
|
||||
#, python-format
|
||||
msgid "%(process_name)s created a task from schedule [%(schedule)s]"
|
||||
msgstr "%(process_name)s a créé une tâche à partir de Schedule [%(schedule)s]"
|
||||
|
||||
#: django_q/cluster.py:718
|
||||
msgid "Skipping cpu affinity because psutil was not found."
|
||||
msgstr "Sauter l'affinité du processeur parce que psutil n'a pas été trouvé."
|
||||
|
||||
#: django_q/cluster.py:723
|
||||
msgid "Faking cpu affinity because it is not supported on this platform"
|
||||
msgstr ""
|
||||
"Simulation de l'affinité du processeur parce qu'elle n'est pas supportée sur "
|
||||
"cette plateforme."
|
||||
|
||||
#: django_q/cluster.py:744
|
||||
#, python-format
|
||||
msgid "%(pid)s will use cpu %(affinity)s"
|
||||
msgstr "%(pid)s utilisera le CPU %(affinity)s"
|
||||
|
||||
#: django_q/conf.py:85
|
||||
#, python-format
|
||||
msgid ""
|
||||
"SAVE_LIMIT_PER (%(option)s) is not a valid option. Options are: 'group', "
|
||||
"'name', 'func' and None. Default is None."
|
||||
msgstr ""
|
||||
"SAVE_LIMIT_PER (%(option)s) n'est pas une option valide. Les options sont : "
|
||||
"'group', 'name', 'func' et None. La valeur par défaut est None."
|
||||
|
||||
#. Translators: Cluster status descriptions
|
||||
#: django_q/conf.py:194
|
||||
msgid "Starting"
|
||||
msgstr "Démarrage"
|
||||
|
||||
#: conf.py:170
|
||||
#: django_q/conf.py:195
|
||||
msgid "Working"
|
||||
msgstr "Actif"
|
||||
|
||||
#: conf.py:171
|
||||
#: django_q/conf.py:196
|
||||
msgid "Idle"
|
||||
msgstr "En attente"
|
||||
|
||||
#: conf.py:172
|
||||
#: django_q/conf.py:197
|
||||
msgid "Stopped"
|
||||
msgstr "Arrêté"
|
||||
|
||||
#: conf.py:173
|
||||
#: django_q/conf.py:198
|
||||
msgid "Stopping"
|
||||
msgstr "En cours d’arrêt"
|
||||
|
||||
#: management/commands/qcluster.py:9
|
||||
#. Translators: help text for qcluster management command
|
||||
#: django_q/management/commands/qcluster.py:9
|
||||
msgid "Starts a Django Q Cluster."
|
||||
msgstr "Démarre un cluster Django Q."
|
||||
|
||||
#: management/commands/qinfo.py:11
|
||||
#. Translators: help text for qinfo management command
|
||||
#: django_q/management/commands/qinfo.py:11
|
||||
msgid "General information over all clusters."
|
||||
msgstr "Informations générales sur tous les clusters."
|
||||
|
||||
#: management/commands/qmonitor.py:9
|
||||
#. Translators: help text for qmemory management command
|
||||
#: django_q/management/commands/qmemory.py:9
|
||||
#, fuzzy
|
||||
#| msgid "Monitors Q Cluster activity"
|
||||
msgid "Monitors Q Cluster memory usage"
|
||||
msgstr "Surveille l'utilisation de la mémoire du cluster Q"
|
||||
|
||||
#. Translators: help text for qmonitor management command
|
||||
#: django_q/management/commands/qmonitor.py:9
|
||||
msgid "Monitors Q Cluster activity"
|
||||
msgstr "Activité du cluster Moniteur Q"
|
||||
|
||||
#: models.py:104
|
||||
#: django_q/models.py:119
|
||||
msgid "Successful task"
|
||||
msgstr "Tâche réussie"
|
||||
|
||||
#: models.py:105
|
||||
#: django_q/models.py:120
|
||||
msgid "Successful tasks"
|
||||
msgstr "Tâches réussies"
|
||||
|
||||
#: models.py:121
|
||||
#: django_q/models.py:135
|
||||
msgid "Failed task"
|
||||
msgstr "Tâche échoué"
|
||||
|
||||
#: models.py:122
|
||||
#: django_q/models.py:136
|
||||
msgid "Failed tasks"
|
||||
msgstr "Tâches échouées"
|
||||
|
||||
#: models.py:131
|
||||
#: django_q/models.py:160
|
||||
msgid "e.g. 1, 2, 'John'"
|
||||
msgstr "ex. 1, 2, ‘Jean’"
|
||||
|
||||
#: models.py:132
|
||||
#: django_q/models.py:162
|
||||
msgid "e.g. x=1, y=2, name='John'"
|
||||
msgstr "p. ex. x = 1, y = 2, Nom = ‘Jean’"
|
||||
|
||||
#: models.py:142
|
||||
#: django_q/models.py:176
|
||||
msgid "Once"
|
||||
msgstr "Une fois"
|
||||
|
||||
#: models.py:143
|
||||
#: django_q/models.py:177
|
||||
msgid "Minutes"
|
||||
msgstr "Minutes"
|
||||
|
||||
#: models.py:144
|
||||
#: django_q/models.py:178
|
||||
msgid "Hourly"
|
||||
msgstr "Toutes les heures"
|
||||
|
||||
#: models.py:145
|
||||
#: django_q/models.py:179
|
||||
msgid "Daily"
|
||||
msgstr "Quotidien"
|
||||
|
||||
#: models.py:146
|
||||
#: django_q/models.py:180
|
||||
msgid "Weekly"
|
||||
msgstr "Hebdomadaire"
|
||||
|
||||
#: models.py:147
|
||||
#: django_q/models.py:181
|
||||
#, fuzzy
|
||||
#| msgid "Weekly"
|
||||
msgid "Biweekly"
|
||||
msgstr "Bihebdomadaire"
|
||||
|
||||
#: django_q/models.py:182
|
||||
msgid "Monthly"
|
||||
msgstr "Mensuel"
|
||||
|
||||
#: models.py:148
|
||||
#: django_q/models.py:183
|
||||
#, fuzzy
|
||||
#| msgid "Monthly"
|
||||
msgid "Bimonthly"
|
||||
msgstr "Bimestriel"
|
||||
|
||||
#: django_q/models.py:184
|
||||
msgid "Quarterly"
|
||||
msgstr "Tous les quart-d’heure"
|
||||
|
||||
#: models.py:149
|
||||
#: django_q/models.py:185
|
||||
msgid "Yearly"
|
||||
msgstr "Annuel"
|
||||
|
||||
#: models.py:151
|
||||
#: django_q/models.py:186
|
||||
msgid "Cron"
|
||||
msgstr "Cron"
|
||||
|
||||
#: django_q/models.py:189
|
||||
msgid "Schedule Type"
|
||||
msgstr "Type de plannification"
|
||||
|
||||
#: models.py:153
|
||||
#: django_q/models.py:192
|
||||
msgid "Number of minutes for the Minutes type"
|
||||
msgstr "Nombre de minutes pour le type de minutes"
|
||||
|
||||
#: models.py:154
|
||||
#: django_q/models.py:195
|
||||
msgid "Repeats"
|
||||
msgstr "Répéter"
|
||||
|
||||
#: models.py:154
|
||||
#: django_q/models.py:195
|
||||
msgid "n = n times, -1 = forever"
|
||||
msgstr "n = n fois,-1 = Toujours"
|
||||
|
||||
#: models.py:155
|
||||
#: django_q/models.py:198
|
||||
msgid "Next Run"
|
||||
msgstr "Prochaine exécution"
|
||||
|
||||
#: models.py:180
|
||||
#: django_q/models.py:205
|
||||
msgid "Cron expression"
|
||||
msgstr "Expression du Cron"
|
||||
|
||||
#: django_q/models.py:235
|
||||
msgid "Scheduled task"
|
||||
msgstr "Tâche planifiée"
|
||||
|
||||
#: models.py:181
|
||||
#: django_q/models.py:236
|
||||
msgid "Scheduled tasks"
|
||||
msgstr "Tâches planifiées"
|
||||
|
||||
#: models.py:204
|
||||
#: django_q/models.py:262
|
||||
msgid "Queued task"
|
||||
msgstr "Tâche en file d'attente"
|
||||
|
||||
#: models.py:205
|
||||
#: django_q/models.py:263
|
||||
msgid "Queued tasks"
|
||||
msgstr "Tâches en file d'attente"
|
||||
|
||||
#: monitor.py:33
|
||||
#: django_q/monitor.py:62 django_q/monitor.py:339
|
||||
msgid "Host"
|
||||
msgstr "Hôte"
|
||||
|
||||
#: monitor.py:34
|
||||
#: django_q/monitor.py:66 django_q/monitor.py:343 django_q/monitor.py:450
|
||||
msgid "Id"
|
||||
msgstr "Id"
|
||||
|
||||
#: monitor.py:35
|
||||
#: django_q/monitor.py:70
|
||||
msgid "State"
|
||||
msgstr "Statut"
|
||||
|
||||
#: monitor.py:36
|
||||
#: django_q/monitor.py:74
|
||||
msgid "Pool"
|
||||
msgstr "Piscine"
|
||||
|
||||
#: monitor.py:37
|
||||
#: django_q/monitor.py:78
|
||||
msgid "TQ"
|
||||
msgstr "TQ"
|
||||
|
||||
#: monitor.py:38
|
||||
#: django_q/monitor.py:82
|
||||
msgid "RQ"
|
||||
msgstr "RQ"
|
||||
|
||||
#: monitor.py:39
|
||||
#: django_q/monitor.py:86
|
||||
msgid "RC"
|
||||
msgstr "RC"
|
||||
|
||||
#: monitor.py:40
|
||||
#: django_q/monitor.py:90
|
||||
msgid "Up"
|
||||
msgstr "Haut"
|
||||
|
||||
#: monitor.py:90 monitor.py:165
|
||||
#: django_q/monitor.py:170 django_q/monitor.py:279
|
||||
msgid "Queued"
|
||||
msgstr "En file d'attente"
|
||||
|
||||
#: monitor.py:92
|
||||
#: django_q/monitor.py:178
|
||||
msgid "Success"
|
||||
msgstr "Succès"
|
||||
|
||||
#: monitor.py:95 monitor.py:173
|
||||
#: django_q/monitor.py:188 django_q/monitor.py:287
|
||||
msgid "Failures"
|
||||
msgstr "Défaillances"
|
||||
|
||||
#: monitor.py:101
|
||||
#: django_q/monitor.py:199 django_q/monitor.py:485
|
||||
msgid "[Press q to quit]"
|
||||
msgstr "[appuyez sur q pour quitter]"
|
||||
|
||||
#: monitor.py:120
|
||||
#: django_q/monitor.py:223
|
||||
msgid "day"
|
||||
msgstr "jour"
|
||||
|
||||
#: monitor.py:137
|
||||
#: django_q/monitor.py:244
|
||||
msgid "second"
|
||||
msgstr "seconde"
|
||||
|
||||
#: monitor.py:140
|
||||
#: django_q/monitor.py:247
|
||||
msgid "minute"
|
||||
msgstr "minute"
|
||||
|
||||
#: monitor.py:143
|
||||
#: django_q/monitor.py:250
|
||||
msgid "hour"
|
||||
msgstr "heure"
|
||||
|
||||
#: monitor.py:151
|
||||
msgid "-- {} {} on {} --"
|
||||
msgstr "--{} {} sur {}--"
|
||||
#: django_q/monitor.py:260
|
||||
#, python-format
|
||||
msgid "-- %(prefix)s %(version)s on %(info)s --"
|
||||
msgstr "--%(prefix)s %(version)s sur %(info)s --"
|
||||
|
||||
#: monitor.py:153
|
||||
#: django_q/monitor.py:266
|
||||
msgid "Clusters"
|
||||
msgstr "Grappes"
|
||||
|
||||
#: monitor.py:157
|
||||
#: django_q/monitor.py:270
|
||||
msgid "Workers"
|
||||
msgstr "Processus"
|
||||
|
||||
#: monitor.py:161
|
||||
#: django_q/monitor.py:274
|
||||
msgid "Restarts"
|
||||
msgstr "Redémarrages"
|
||||
|
||||
#: monitor.py:169
|
||||
#: django_q/monitor.py:283
|
||||
msgid "Successes"
|
||||
msgstr "Succès"
|
||||
|
||||
#: monitor.py:177
|
||||
#: django_q/monitor.py:292
|
||||
msgid "Schedules"
|
||||
msgstr "Planifications"
|
||||
|
||||
#: monitor.py:181
|
||||
msgid "Tasks/{}"
|
||||
msgstr "Tâches/{}"
|
||||
#: django_q/monitor.py:296
|
||||
#, python-format
|
||||
msgid "Tasks/%(per)s"
|
||||
msgstr "Tâches/%(per)s"
|
||||
|
||||
#: monitor.py:185
|
||||
#: django_q/monitor.py:300
|
||||
msgid "Avg time"
|
||||
msgstr "Temps Moyen"
|
||||
|
||||
#: signals.py:21
|
||||
msgid "malformed return hook '{}' for [{}]"
|
||||
msgstr "hook de retour mal formé' {} 'pour [{}]"
|
||||
#: django_q/monitor.py:348
|
||||
msgid "Available (%)"
|
||||
msgstr ""
|
||||
|
||||
#: signals.py:26
|
||||
msgid "return hook {} failed on [{}] because {}"
|
||||
msgstr "le crochet de retour {} a échoué sur [{}] parce que {}"
|
||||
#: django_q/monitor.py:354
|
||||
msgid "Available (MB)"
|
||||
msgstr "Disponible sur (MB)"
|
||||
|
||||
#: django_q/monitor.py:359
|
||||
msgid "Total (MB)"
|
||||
msgstr "Total (MB)"
|
||||
|
||||
#: django_q/monitor.py:364
|
||||
msgid "Sentinel (MB)"
|
||||
msgstr "Sentinel (MB)"
|
||||
|
||||
#: django_q/monitor.py:370
|
||||
msgid "Monitor (MB)"
|
||||
msgstr "Monitor (MB)"
|
||||
|
||||
#: django_q/monitor.py:376
|
||||
#, fuzzy
|
||||
#| msgid "Workers"
|
||||
msgid "Workers (MB)"
|
||||
msgstr "Processus (MB)"
|
||||
|
||||
#: django_q/monitor.py:478
|
||||
#, python-format
|
||||
msgid "Available lowest (): %(memory_percent)s ((at)s)"
|
||||
msgstr "Disponible le plus bas () : %(memory_percent)s ((at)s)"
|
||||
|
||||
#: django_q/monitor.py:496
|
||||
msgid "No clusters appear to be running."
|
||||
msgstr "Aucun cluster ne semble être en cours d'exécution."
|
||||
|
||||
#: django_q/signals.py:22
|
||||
#, python-format
|
||||
msgid "malformed return hook '%(hook)s' for [%(name)s]"
|
||||
msgstr "hook de retour mal formé' %(hook)s 'pour [%(name)s]"
|
||||
|
||||
#: django_q/signals.py:30
|
||||
#, python-format
|
||||
msgid "return hook %(hook)s failed on [%(name)s] because %(error)s"
|
||||
msgstr "le crochet de retour %(hook)s a échoué sur [%(name)s] parce que %(error)s"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-06-18 23:55+0300\n"
|
||||
"POT-Creation-Date: 2022-11-12 01:47+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Ethem Güner <ethemguener@gmail.com>\n"
|
||||
"Language-Team: \n"
|
||||
@@ -17,166 +17,199 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
#: django_q/admin.py:40
|
||||
|
||||
#: django_q/admin.py:43
|
||||
msgid "Resubmit selected tasks to queue"
|
||||
msgstr "Seçili işleri kuyruğa tekrar gönder"
|
||||
|
||||
#: django_q/brokers/disque.py:60
|
||||
msgid "No Disque nodes configured"
|
||||
msgstr "Disque nodları konfigüre edilmemiş"
|
||||
#: django_q/admin.py:98 django_q/models.py:228
|
||||
#, fuzzy
|
||||
#| msgid "Success"
|
||||
msgid "success"
|
||||
msgstr "başarılı olanlar"
|
||||
|
||||
#: django_q/brokers/disque.py:77
|
||||
msgid "Could not connect to any Disque nodes"
|
||||
msgstr "Herhangi bir Disque noduna bağlanılamadı"
|
||||
|
||||
#: django_q/cluster.py:79
|
||||
#, python-brace-format
|
||||
msgid "Q Cluster {self.name} starting."
|
||||
msgstr "Q Cluster {self.name} başlatılıyor."
|
||||
|
||||
#: django_q/cluster.py:87
|
||||
#, python-brace-format
|
||||
msgid "Q Cluster {self.name} stopping."
|
||||
msgstr "Q Cluster {self.name} durduruluyor."
|
||||
|
||||
#: django_q/cluster.py:90
|
||||
#, python-brace-format
|
||||
msgid "Q Cluster {self.name} has stopped."
|
||||
msgstr "Q Cluster {self.name} durduruldu."
|
||||
|
||||
#: django_q/cluster.py:98
|
||||
msgid ""
|
||||
"{current_process().name} got signal {Conf.SIGNAL_NAMES.get(signum, \"UNKNOWN"
|
||||
"\")}"
|
||||
#: django_q/admin.py:109 django_q/models.py:230
|
||||
msgid "last_run"
|
||||
msgstr ""
|
||||
"{current_process().name} şu sinyali aldı {Conf.SIGNAL_NAMES.get(signum, \"UNKNOWN"
|
||||
"\")}"
|
||||
|
||||
#: django_q/cluster.py:220
|
||||
#, python-brace-format
|
||||
msgid "reincarnated monitor {process.name} after sudden death"
|
||||
msgstr "Monitor {process.name} ani ölüm sonrası tekrar dirildi"
|
||||
#: django_q/cluster.py:77
|
||||
#, python-format
|
||||
msgid "Q Cluster %(name)s starting."
|
||||
msgstr "Q Cluster %(name)s başlatılıyor."
|
||||
|
||||
#: django_q/cluster.py:223
|
||||
#, python-brace-format
|
||||
msgid "reincarnated pusher {process.name} after sudden death"
|
||||
msgstr "Pusher {process.name} ani ölüm sonrası tekrar dirildi"
|
||||
#: django_q/cluster.py:85
|
||||
#, python-format
|
||||
msgid "Q Cluster %(name)s stopping."
|
||||
msgstr "Q Cluster %(name)s durduruluyor."
|
||||
|
||||
#: django_q/cluster.py:230
|
||||
#, python-brace-format
|
||||
msgid "reincarnated worker {process.name} after timeout"
|
||||
msgstr "Worker {process.name} zaman aşımı sonrası tekrar dirildi"
|
||||
#: django_q/cluster.py:88
|
||||
#, python-format
|
||||
msgid "Q Cluster %(name)s has stopped."
|
||||
msgstr "Q Cluster %(name)s durduruldu."
|
||||
|
||||
#: django_q/cluster.py:232
|
||||
#, python-brace-format
|
||||
msgid "recycled worker {process.name}"
|
||||
msgstr "Worker {process.name} geri döndürüldü"
|
||||
#: django_q/cluster.py:96
|
||||
#, python-format
|
||||
msgid "%(name)s got signal %(signal)s"
|
||||
msgstr "%(name)s, %(signal)s pid'inde izleniyor/monitoring yapılıyor."
|
||||
|
||||
#: django_q/cluster.py:234
|
||||
#, python-brace-format
|
||||
msgid "reincarnated worker {process.name} after death"
|
||||
msgstr "Worker {process.name} ani ölüm sonrası tekrar dirildi"
|
||||
#: django_q/cluster.py:219
|
||||
#, python-format
|
||||
msgid "reincarnated monitor %(name)s after sudden death"
|
||||
msgstr "Monitor %(name)s ani ölüm sonrası tekrar dirildi"
|
||||
|
||||
#: django_q/cluster.py:222
|
||||
#, python-format
|
||||
msgid "reincarnated pusher %(name)s after sudden death"
|
||||
msgstr "Pusher %(name)s ani ölüm sonrası tekrar dirildi"
|
||||
|
||||
#: django_q/cluster.py:229
|
||||
#, python-format
|
||||
msgid "reincarnated worker %(name)s after timeout"
|
||||
msgstr "Worker %(name)s zaman aşımı sonrası tekrar dirildi"
|
||||
|
||||
#: django_q/cluster.py:231
|
||||
#, python-format
|
||||
msgid "recycled worker %(name)s"
|
||||
msgstr "Worker %(name)s geri döndürüldü"
|
||||
|
||||
#: django_q/cluster.py:233
|
||||
#, python-format
|
||||
msgid "reincarnated worker %(name)s after death"
|
||||
msgstr "Worker %(name)s ani ölüm sonrası tekrar dirildi"
|
||||
|
||||
#: django_q/cluster.py:256
|
||||
msgid ""
|
||||
"{current_process().name} guarding cluster {humanize(self.cluster_id.hex)}"
|
||||
msgstr "{current_process().name}, {humanize(self.cluster_id.hex)} cluster'ını koruyor"
|
||||
#, python-format
|
||||
msgid "%(name)s guarding cluster %(cluster_name)s"
|
||||
msgstr "%(name)s, %(cluster_name)s cluster'ını koruyor"
|
||||
|
||||
#: django_q/cluster.py:261
|
||||
msgid "Q Cluster {humanize(self.cluster_id.hex)} running."
|
||||
msgstr "Q Cluster {humanize(self.cluster_id.hex)} çalışıyor."
|
||||
#, python-format
|
||||
msgid "Q Cluster %(cluster_name)s running."
|
||||
msgstr "Q Cluster %(cluster_name)s başlatılıyor."
|
||||
|
||||
#: django_q/cluster.py:295
|
||||
#, python-brace-format
|
||||
msgid "{name} stopping cluster processes"
|
||||
msgstr "Cluster {name} işlemleri durduruluyor."
|
||||
#, python-format
|
||||
msgid "%(name)s stopping cluster processes"
|
||||
msgstr "Cluster %(name)s işlemleri durduruluyor."
|
||||
|
||||
#: django_q/cluster.py:320
|
||||
#, python-brace-format
|
||||
msgid "{name} waiting for the monitor."
|
||||
msgstr "{name} monitor için bekliyor."
|
||||
#, python-format
|
||||
msgid "%(name)s waiting for the monitor."
|
||||
msgstr "%(name)s monitor için bekliyor."
|
||||
|
||||
#: django_q/cluster.py:342
|
||||
msgid "{current_process().name} pushing tasks at {current_process().pid}"
|
||||
msgstr "{current_process().name, işleri {current_process().pid} pid'ine gönderiyor."
|
||||
#, python-format
|
||||
msgid "%(process_name)s pushing tasks at %(id)s"
|
||||
msgstr "%(process_name)s, işleri %(id)s pid'ine gönderiyor."
|
||||
|
||||
#: django_q/cluster.py:363
|
||||
#, python-brace-format
|
||||
msgid "queueing from {broker.list_key}"
|
||||
#, python-format
|
||||
msgid "queueing from %(list_key)s"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/cluster.py:366
|
||||
msgid "{current_process().name} stopped pushing tasks"
|
||||
msgstr "{current_process().name} işleri göndermeyi durdurdu"
|
||||
#, python-format
|
||||
msgid "%(name)s stopped pushing tasks"
|
||||
msgstr "%(name)s işleri göndermeyi durdurdu"
|
||||
|
||||
#: django_q/cluster.py:378
|
||||
msgid "{name} monitoring at {current_process().pid}"
|
||||
msgstr "{name}, {current_process().pid} pid'inde izleniyor/monitoring yapılıyor."
|
||||
#, python-format
|
||||
msgid "%(name)s monitoring at %(id)s"
|
||||
msgstr "%(name)s, %(id)s pid'inde izleniyor/monitoring yapılıyor."
|
||||
|
||||
#: django_q/cluster.py:394
|
||||
msgid "Processed [{task['name']}]"
|
||||
msgstr "[{task['name']}] işlendi."
|
||||
|
||||
#: django_q/cluster.py:397
|
||||
msgid "Failed [{task['name']}] - {task['result']}"
|
||||
msgstr "[{task['name']}] - {task['result']} başarısız oldu"
|
||||
#: django_q/cluster.py:395
|
||||
#, python-format
|
||||
msgid "Processed '%(info_name)s' (%(task_name)s)"
|
||||
msgstr "[%(task_name)s] - '%(info_name)s işlendi."
|
||||
|
||||
#: django_q/cluster.py:398
|
||||
#, python-brace-format
|
||||
msgid "{name} stopped monitoring results"
|
||||
msgstr "{name} sonuçları göstermeyi bıraktı"
|
||||
#, python-format
|
||||
msgid "Failed '%(info_name)s' (%(task_name)s) - %(task_result)s"
|
||||
msgstr "[%(task_name)s] - '%(info_name)s' - %(task_result)s başarısız oldu"
|
||||
|
||||
#: django_q/cluster.py:412
|
||||
msgid "{name} ready for work at {current_process().pid}"
|
||||
msgstr "{name}, {current_process().pid} pid'inde çalışmaya hazır"
|
||||
#: django_q/cluster.py:399
|
||||
#, python-format
|
||||
msgid "%(name)s stopped monitoring results"
|
||||
msgstr "%(name)s sonuçları göstermeyi bıraktı"
|
||||
|
||||
#: django_q/cluster.py:422
|
||||
msgid "{name} processing [{task[\"name\"]}]"
|
||||
msgstr "{name}, [{task[\"name\"]}] işlerini işiyor"
|
||||
#: django_q/cluster.py:413
|
||||
#, python-format
|
||||
msgid "%(proc_name)s ready for work at %(id)s"
|
||||
msgstr "%(proc_name)s, %(id)s pid'inde çalışmaya hazır"
|
||||
|
||||
#: django_q/cluster.py:453
|
||||
#, python-brace-format
|
||||
msgid "{name} stopped doing work"
|
||||
msgstr "{name} çalışmayı bıraktı"
|
||||
#: django_q/cluster.py:425
|
||||
#, python-format
|
||||
msgid "%(proc_name)s processing '%(func_name)s' (%(task_name)s)"
|
||||
msgstr "%(proc_name)s, '%(func_name)s' [%(task_name)s] işlerini işiyor"
|
||||
|
||||
#: django_q/cluster.py:635 django_q/models.py:143
|
||||
#: django_q/cluster.py:440
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Could not process '%(func_name)s'. Check the location of the function and "
|
||||
"the args/kwargs."
|
||||
msgstr ""
|
||||
"%(func_name)s' işlenemedi. İşlevin konumunu ve args/kwargs öğelerini kontrol "
|
||||
"edin."
|
||||
|
||||
#: django_q/cluster.py:456
|
||||
#, python-format
|
||||
msgid "%(proc_name)s stopped doing work"
|
||||
msgstr "%(proc_name)s çalışmayı bıraktı"
|
||||
|
||||
#: django_q/cluster.py:649 django_q/models.py:144
|
||||
msgid "Please install croniter to enable cron expressions"
|
||||
msgstr "Cron expressions'ları açmak için croniter yükleyin"
|
||||
|
||||
#: django_q/cluster.py:665
|
||||
msgid ""
|
||||
"{current_process().name} failed to create a task from schedule [{s.name or s."
|
||||
"id}]"
|
||||
msgstr ""
|
||||
#: django_q/cluster.py:672
|
||||
#, python-format
|
||||
msgid "%(process_name)s failed to create a task from schedule [%(schedule)s]"
|
||||
msgstr "%(process_name)s programdan bir görev oluşturamadı [%(schedule)s]"
|
||||
|
||||
#: django_q/cluster.py:671
|
||||
msgid ""
|
||||
"{current_process().name} created a task from schedule [{s.name or s.id}]"
|
||||
msgstr ""
|
||||
#: django_q/cluster.py:678
|
||||
#, python-format
|
||||
msgid "%(process_name)s created a task from schedule [%(schedule)s]"
|
||||
msgstr "%(process_name)s programdan bir görev oluşturamadı [%(schedule)s]"
|
||||
|
||||
#: django_q/cluster.py:737
|
||||
#, python-brace-format
|
||||
msgid "{pid} will use cpu {affinity}"
|
||||
#: django_q/cluster.py:718
|
||||
msgid "Skipping cpu affinity because psutil was not found."
|
||||
msgstr "Psutil bulunamadığı için cpu benzeşimi atlanıyor."
|
||||
|
||||
#: django_q/cluster.py:723
|
||||
msgid "Faking cpu affinity because it is not supported on this platform"
|
||||
msgstr "Bu platformda desteklenmediği için sahte cpu benzeşimi"
|
||||
|
||||
#: django_q/cluster.py:744
|
||||
#, python-format
|
||||
msgid "%(pid)s will use cpu %(affinity)s"
|
||||
msgstr "%(pid)s cpu %(affinity)s kullanacaktır"
|
||||
|
||||
#: django_q/conf.py:85
|
||||
#, python-format
|
||||
msgid ""
|
||||
"SAVE_LIMIT_PER (%(option)s) is not a valid option. Options are: 'group', "
|
||||
"'name', 'func' and None. Default is None."
|
||||
msgstr ""
|
||||
"SAVE_LIMIT_PER (%(option)s) geçerli bir seçenek değil. Seçenekler şunlardır: "
|
||||
"'group', 'name', 'func' ve None. Varsayılan değer None'dır."
|
||||
|
||||
#. Translators: Cluster status descriptions
|
||||
#: django_q/conf.py:196
|
||||
#: django_q/conf.py:194
|
||||
msgid "Starting"
|
||||
msgstr "Başlıyor"
|
||||
|
||||
#: django_q/conf.py:197
|
||||
#: django_q/conf.py:195
|
||||
msgid "Working"
|
||||
msgstr "Çalışıyor"
|
||||
|
||||
#: django_q/conf.py:198
|
||||
#: django_q/conf.py:196
|
||||
msgid "Idle"
|
||||
msgstr "Boşta"
|
||||
|
||||
#: django_q/conf.py:199
|
||||
#: django_q/conf.py:197
|
||||
msgid "Stopped"
|
||||
msgstr "Durdu"
|
||||
|
||||
#: django_q/conf.py:200
|
||||
#: django_q/conf.py:198
|
||||
msgid "Stopping"
|
||||
msgstr "Durduruluyor"
|
||||
|
||||
@@ -200,239 +233,255 @@ msgstr "Q Cluster'ın bellek kullanımını izler"
|
||||
msgid "Monitors Q Cluster activity"
|
||||
msgstr "Q Cluster'ın aktivitelerini izler"
|
||||
|
||||
#: django_q/models.py:118
|
||||
#: django_q/models.py:119
|
||||
msgid "Successful task"
|
||||
msgstr "Başarılı iş"
|
||||
|
||||
#: django_q/models.py:119
|
||||
#: django_q/models.py:120
|
||||
msgid "Successful tasks"
|
||||
msgstr "Başarılı işler"
|
||||
|
||||
#: django_q/models.py:134
|
||||
#: django_q/models.py:135
|
||||
msgid "Failed task"
|
||||
msgstr "Başarısız iş"
|
||||
|
||||
#: django_q/models.py:135
|
||||
#: django_q/models.py:136
|
||||
msgid "Failed tasks"
|
||||
msgstr "Başarısız işler"
|
||||
|
||||
#: django_q/models.py:159
|
||||
#: django_q/models.py:160
|
||||
msgid "e.g. 1, 2, 'John'"
|
||||
msgstr "Örneğin: 1, 2, 'Melih'"
|
||||
|
||||
#: django_q/models.py:161
|
||||
#: django_q/models.py:162
|
||||
msgid "e.g. x=1, y=2, name='John'"
|
||||
msgstr "Örneğin: x=1, y=2, name='Melih'"
|
||||
|
||||
#: django_q/models.py:173
|
||||
#: django_q/models.py:176
|
||||
msgid "Once"
|
||||
msgstr "Bir kere"
|
||||
|
||||
#: django_q/models.py:174
|
||||
#: django_q/models.py:177
|
||||
msgid "Minutes"
|
||||
msgstr "Dakika"
|
||||
|
||||
#: django_q/models.py:175
|
||||
#: django_q/models.py:178
|
||||
msgid "Hourly"
|
||||
msgstr "Saatlik"
|
||||
|
||||
#: django_q/models.py:176
|
||||
#: django_q/models.py:179
|
||||
msgid "Daily"
|
||||
msgstr "Günlük"
|
||||
|
||||
#: django_q/models.py:177
|
||||
#: django_q/models.py:180
|
||||
msgid "Weekly"
|
||||
msgstr "Haftalık"
|
||||
|
||||
#: django_q/models.py:178
|
||||
#: django_q/models.py:181
|
||||
#, fuzzy
|
||||
#| msgid "Weekly"
|
||||
msgid "Biweekly"
|
||||
msgstr "İki haftada bir"
|
||||
|
||||
#: django_q/models.py:182
|
||||
msgid "Monthly"
|
||||
msgstr "Aylık"
|
||||
|
||||
#: django_q/models.py:179
|
||||
#: django_q/models.py:183
|
||||
#, fuzzy
|
||||
#| msgid "Monthly"
|
||||
msgid "Bimonthly"
|
||||
msgstr "İki ayda bir"
|
||||
|
||||
#: django_q/models.py:184
|
||||
msgid "Quarterly"
|
||||
msgstr "Bir Çeyrek (3 Ay)"
|
||||
|
||||
#: django_q/models.py:180
|
||||
#: django_q/models.py:185
|
||||
msgid "Yearly"
|
||||
msgstr "Yıllık"
|
||||
|
||||
#: django_q/models.py:181
|
||||
#: django_q/models.py:186
|
||||
msgid "Cron"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/models.py:184
|
||||
#: django_q/models.py:189
|
||||
msgid "Schedule Type"
|
||||
msgstr "Zamanlama Tipi"
|
||||
|
||||
#: django_q/models.py:187
|
||||
#: django_q/models.py:192
|
||||
msgid "Number of minutes for the Minutes type"
|
||||
msgstr "Dakika tipine göre dakika sayısı"
|
||||
|
||||
#: django_q/models.py:190
|
||||
#: django_q/models.py:195
|
||||
msgid "Repeats"
|
||||
msgstr "Tekrar eder"
|
||||
|
||||
#: django_q/models.py:190
|
||||
#: django_q/models.py:195
|
||||
msgid "n = n times, -1 = forever"
|
||||
msgstr "n = n kere, -1 = sonsuza kadar"
|
||||
|
||||
#: django_q/models.py:193
|
||||
#: django_q/models.py:198
|
||||
msgid "Next Run"
|
||||
msgstr "Bir dahaki çalışma tarihi"
|
||||
|
||||
#: django_q/models.py:200
|
||||
#: django_q/models.py:205
|
||||
msgid "Cron expression"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/models.py:227
|
||||
#: django_q/models.py:235
|
||||
msgid "Scheduled task"
|
||||
msgstr "Zamanlanmış iş"
|
||||
|
||||
#: django_q/models.py:228
|
||||
#: django_q/models.py:236
|
||||
msgid "Scheduled tasks"
|
||||
msgstr "Zamanlanmış işler"
|
||||
|
||||
#: django_q/models.py:251
|
||||
#: django_q/models.py:262
|
||||
msgid "Queued task"
|
||||
msgstr "Sıraya alınmış iş"
|
||||
|
||||
#: django_q/models.py:252
|
||||
#: django_q/models.py:263
|
||||
msgid "Queued tasks"
|
||||
msgstr "Sıraya alınmış işler"
|
||||
|
||||
#: django_q/monitor.py:54 django_q/monitor.py:322
|
||||
#: django_q/monitor.py:62 django_q/monitor.py:339
|
||||
msgid "Host"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/monitor.py:58 django_q/monitor.py:326 django_q/monitor.py:433
|
||||
#: django_q/monitor.py:66 django_q/monitor.py:343 django_q/monitor.py:450
|
||||
msgid "Id"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/monitor.py:62
|
||||
#: django_q/monitor.py:70
|
||||
msgid "State"
|
||||
msgstr "Durum"
|
||||
|
||||
#: django_q/monitor.py:66
|
||||
#: django_q/monitor.py:74
|
||||
msgid "Pool"
|
||||
msgstr "Havuz"
|
||||
|
||||
#: django_q/monitor.py:70
|
||||
#: django_q/monitor.py:78
|
||||
msgid "TQ"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/monitor.py:74
|
||||
#: django_q/monitor.py:82
|
||||
msgid "RQ"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/monitor.py:78
|
||||
#: django_q/monitor.py:86
|
||||
msgid "RC"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/monitor.py:82
|
||||
#: django_q/monitor.py:90
|
||||
msgid "Up"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/monitor.py:162 django_q/monitor.py:266
|
||||
#: django_q/monitor.py:170 django_q/monitor.py:279
|
||||
msgid "Queued"
|
||||
msgstr "Sıraya alınmış"
|
||||
|
||||
#: django_q/monitor.py:170
|
||||
#: django_q/monitor.py:178
|
||||
msgid "Success"
|
||||
msgstr "Başarılı olanlar"
|
||||
|
||||
#: django_q/monitor.py:180 django_q/monitor.py:274
|
||||
#: django_q/monitor.py:188 django_q/monitor.py:287
|
||||
msgid "Failures"
|
||||
msgstr "Başarısız olanlar"
|
||||
|
||||
#: django_q/monitor.py:191
|
||||
#: django_q/monitor.py:199 django_q/monitor.py:485
|
||||
msgid "[Press q to quit]"
|
||||
msgstr "[Çıkmak için q'ya basın]"
|
||||
|
||||
#: django_q/monitor.py:210
|
||||
#: django_q/monitor.py:223
|
||||
msgid "day"
|
||||
msgstr "gün"
|
||||
|
||||
#: django_q/monitor.py:231
|
||||
#: django_q/monitor.py:244
|
||||
msgid "second"
|
||||
msgstr "saniye"
|
||||
|
||||
#: django_q/monitor.py:234
|
||||
#: django_q/monitor.py:247
|
||||
msgid "minute"
|
||||
msgstr "dakika"
|
||||
|
||||
#: django_q/monitor.py:237
|
||||
#: django_q/monitor.py:250
|
||||
msgid "hour"
|
||||
msgstr "saat"
|
||||
|
||||
#: django_q/monitor.py:247
|
||||
msgid ""
|
||||
"-- {Conf.PREFIX.capitalize()} { \".\".join(str(v) for v in VERSION)} on "
|
||||
"{broker.info()} --"
|
||||
msgstr ""
|
||||
#: django_q/monitor.py:260
|
||||
#, python-format
|
||||
msgid "-- %(prefix)s %(version)s on %(info)s --"
|
||||
msgstr "-- %(prefix)s %(version)s üzerinde %(info)s --"
|
||||
|
||||
#: django_q/monitor.py:253
|
||||
#: django_q/monitor.py:266
|
||||
msgid "Clusters"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/monitor.py:257
|
||||
#: django_q/monitor.py:270
|
||||
msgid "Workers"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/monitor.py:261
|
||||
#: django_q/monitor.py:274
|
||||
msgid "Restarts"
|
||||
msgstr "Yeniden çalıştırmalar"
|
||||
|
||||
#: django_q/monitor.py:270
|
||||
#: django_q/monitor.py:283
|
||||
msgid "Successes"
|
||||
msgstr "Başarılı olanlar"
|
||||
|
||||
#: django_q/monitor.py:279
|
||||
#: django_q/monitor.py:292
|
||||
msgid "Schedules"
|
||||
msgstr "Zamanlanmışlar"
|
||||
|
||||
#: django_q/monitor.py:283
|
||||
#, python-brace-format
|
||||
msgid "Tasks/{per}"
|
||||
msgstr "İş/{per}"
|
||||
#: django_q/monitor.py:296
|
||||
#, python-format
|
||||
msgid "Tasks/%(per)s"
|
||||
msgstr "İş/%(per)s"
|
||||
|
||||
#: django_q/monitor.py:287
|
||||
#: django_q/monitor.py:300
|
||||
msgid "Avg time"
|
||||
msgstr "Ortalama süre"
|
||||
|
||||
#: django_q/monitor.py:331
|
||||
#: django_q/monitor.py:348
|
||||
msgid "Available (%)"
|
||||
msgstr "Müsait (%) "
|
||||
|
||||
#: django_q/monitor.py:337
|
||||
#: django_q/monitor.py:354
|
||||
msgid "Available (MB)"
|
||||
msgstr "Müsait (MB)"
|
||||
|
||||
#: django_q/monitor.py:342
|
||||
#: django_q/monitor.py:359
|
||||
msgid "Total (MB)"
|
||||
msgstr "Toplam (MB)"
|
||||
|
||||
#: django_q/monitor.py:347
|
||||
#: django_q/monitor.py:364
|
||||
msgid "Sentinel (MB)"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/monitor.py:353
|
||||
#: django_q/monitor.py:370
|
||||
msgid "Monitor (MB)"
|
||||
msgstr "İzleme (MB)"
|
||||
|
||||
#: django_q/monitor.py:359
|
||||
#: django_q/monitor.py:376
|
||||
msgid "Workers (MB)"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/monitor.py:461
|
||||
msgid "Available lowest (%): {} ({})"
|
||||
msgstr "Mevcut en düşük (%): {} ({})"
|
||||
#: django_q/monitor.py:478
|
||||
#, python-format
|
||||
msgid "Available lowest (): %(memory_percent)s ((at)s)"
|
||||
msgstr "Mevcut en düşük (): %(memory_percent)s ((at)s)"
|
||||
|
||||
#: django_q/monitor.py:496
|
||||
msgid "No clusters appear to be running."
|
||||
msgstr "Hiçbir küme çalışıyor görünmüyor."
|
||||
|
||||
#: django_q/signals.py:22
|
||||
#, python-brace-format
|
||||
msgid "malformed return hook '{instance.hook}' for [{instance.name}]"
|
||||
#, python-format
|
||||
msgid "malformed return hook '%(hook)s' for [%(name)s]"
|
||||
msgstr ""
|
||||
|
||||
#: django_q/signals.py:30
|
||||
msgid ""
|
||||
"return hook {instance.hook} failed on [{instance.name}] because {str(e)}"
|
||||
#, python-format
|
||||
msgid "return hook %(hook)s failed on [%(name)s] because %(error)s"
|
||||
msgstr ""
|
||||
|
||||
@@ -5,61 +5,142 @@ from django.db import migrations, models
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Schedule',
|
||||
name="Schedule",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||
('func', models.CharField(max_length=256, help_text='e.g. module.tasks.function')),
|
||||
('hook', models.CharField(null=True, blank=True, max_length=256, help_text='e.g. module.tasks.result_function')),
|
||||
('args', models.CharField(null=True, blank=True, max_length=256, help_text="e.g. 1, 2, 'John'")),
|
||||
('kwargs', models.CharField(null=True, blank=True, max_length=256, help_text="e.g. x=1, y=2, name='John'")),
|
||||
('schedule_type', models.CharField(verbose_name='Schedule Type', choices=[('O', 'Once'), ('H', 'Hourly'), ('D', 'Daily'), ('W', 'Weekly'), ('M', 'Monthly'), ('Q', 'Quarterly'), ('Y', 'Yearly')], default='O', max_length=1)),
|
||||
('repeats', models.SmallIntegerField(verbose_name='Repeats', default=-1, help_text='n = n times, -1 = forever')),
|
||||
('next_run', models.DateTimeField(verbose_name='Next Run', default=django.utils.timezone.now, null=True)),
|
||||
('task', models.CharField(editable=False, null=True, max_length=100)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
auto_created=True,
|
||||
serialize=False,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"func",
|
||||
models.CharField(
|
||||
max_length=256, help_text="e.g. module.tasks.function"
|
||||
),
|
||||
),
|
||||
(
|
||||
"hook",
|
||||
models.CharField(
|
||||
null=True,
|
||||
blank=True,
|
||||
max_length=256,
|
||||
help_text="e.g. module.tasks.result_function",
|
||||
),
|
||||
),
|
||||
(
|
||||
"args",
|
||||
models.CharField(
|
||||
null=True,
|
||||
blank=True,
|
||||
max_length=256,
|
||||
help_text="e.g. 1, 2, 'John'",
|
||||
),
|
||||
),
|
||||
(
|
||||
"kwargs",
|
||||
models.CharField(
|
||||
null=True,
|
||||
blank=True,
|
||||
max_length=256,
|
||||
help_text="e.g. x=1, y=2, name='John'",
|
||||
),
|
||||
),
|
||||
(
|
||||
"schedule_type",
|
||||
models.CharField(
|
||||
verbose_name="Schedule Type",
|
||||
choices=[
|
||||
("O", "Once"),
|
||||
("H", "Hourly"),
|
||||
("D", "Daily"),
|
||||
("W", "Weekly"),
|
||||
("M", "Monthly"),
|
||||
("Q", "Quarterly"),
|
||||
("Y", "Yearly"),
|
||||
],
|
||||
default="O",
|
||||
max_length=1,
|
||||
),
|
||||
),
|
||||
(
|
||||
"repeats",
|
||||
models.SmallIntegerField(
|
||||
verbose_name="Repeats",
|
||||
default=-1,
|
||||
help_text="n = n times, -1 = forever",
|
||||
),
|
||||
),
|
||||
(
|
||||
"next_run",
|
||||
models.DateTimeField(
|
||||
verbose_name="Next Run",
|
||||
default=django.utils.timezone.now,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
("task", models.CharField(editable=False, null=True, max_length=100)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Scheduled task',
|
||||
'ordering': ['next_run'],
|
||||
"verbose_name": "Scheduled task",
|
||||
"ordering": ["next_run"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Task',
|
||||
name="Task",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||
('name', models.CharField(editable=False, max_length=100)),
|
||||
('func', models.CharField(max_length=256)),
|
||||
('hook', models.CharField(null=True, max_length=256)),
|
||||
('args', picklefield.fields.PickledObjectField(editable=False, null=True)),
|
||||
('kwargs', picklefield.fields.PickledObjectField(editable=False, null=True)),
|
||||
('result', picklefield.fields.PickledObjectField(editable=False, null=True)),
|
||||
('started', models.DateTimeField(editable=False)),
|
||||
('stopped', models.DateTimeField(editable=False)),
|
||||
('success', models.BooleanField(editable=False, default=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
auto_created=True,
|
||||
serialize=False,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(editable=False, max_length=100)),
|
||||
("func", models.CharField(max_length=256)),
|
||||
("hook", models.CharField(null=True, max_length=256)),
|
||||
(
|
||||
"args",
|
||||
picklefield.fields.PickledObjectField(editable=False, null=True),
|
||||
),
|
||||
(
|
||||
"kwargs",
|
||||
picklefield.fields.PickledObjectField(editable=False, null=True),
|
||||
),
|
||||
(
|
||||
"result",
|
||||
picklefield.fields.PickledObjectField(editable=False, null=True),
|
||||
),
|
||||
("started", models.DateTimeField(editable=False)),
|
||||
("stopped", models.DateTimeField(editable=False)),
|
||||
("success", models.BooleanField(editable=False, default=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Failure',
|
||||
fields=[
|
||||
],
|
||||
name="Failure",
|
||||
fields=[],
|
||||
options={
|
||||
'verbose_name': 'Failed task',
|
||||
'proxy': True,
|
||||
"verbose_name": "Failed task",
|
||||
"proxy": True,
|
||||
},
|
||||
bases=('django_q.task',),
|
||||
bases=("django_q.task",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Success',
|
||||
fields=[
|
||||
],
|
||||
name="Success",
|
||||
fields=[],
|
||||
options={
|
||||
'verbose_name': 'Successful task',
|
||||
'proxy': True,
|
||||
"verbose_name": "Successful task",
|
||||
"proxy": True,
|
||||
},
|
||||
bases=('django_q.task',),
|
||||
bases=("django_q.task",),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,18 +4,22 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0001_initial'),
|
||||
("django_q", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='args',
|
||||
field=models.TextField(help_text="e.g. 1, 2, 'John'", blank=True, null=True),
|
||||
model_name="schedule",
|
||||
name="args",
|
||||
field=models.TextField(
|
||||
help_text="e.g. 1, 2, 'John'", blank=True, null=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='kwargs',
|
||||
field=models.TextField(help_text="e.g. x=1, y=2, name='John'", blank=True, null=True),
|
||||
model_name="schedule",
|
||||
name="kwargs",
|
||||
field=models.TextField(
|
||||
help_text="e.g. x=1, y=2, name='John'", blank=True, null=True
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,29 +4,41 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0002_auto_20150630_1624'),
|
||||
("django_q", "0002_auto_20150630_1624"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='failure',
|
||||
options={'verbose_name_plural': 'Failed tasks', 'verbose_name': 'Failed task'},
|
||||
name="failure",
|
||||
options={
|
||||
"verbose_name_plural": "Failed tasks",
|
||||
"verbose_name": "Failed task",
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='schedule',
|
||||
options={'verbose_name_plural': 'Scheduled tasks', 'ordering': ['next_run'], 'verbose_name': 'Scheduled task'},
|
||||
name="schedule",
|
||||
options={
|
||||
"verbose_name_plural": "Scheduled tasks",
|
||||
"ordering": ["next_run"],
|
||||
"verbose_name": "Scheduled task",
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='success',
|
||||
options={'verbose_name_plural': 'Successful tasks', 'verbose_name': 'Successful task'},
|
||||
name="success",
|
||||
options={
|
||||
"verbose_name_plural": "Successful tasks",
|
||||
"verbose_name": "Successful task",
|
||||
},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='task',
|
||||
name='id',
|
||||
model_name="task",
|
||||
name="id",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='id',
|
||||
field=models.CharField(max_length=32, primary_key=True, editable=False, serialize=False),
|
||||
model_name="task",
|
||||
name="id",
|
||||
field=models.CharField(
|
||||
max_length=32, primary_key=True, editable=False, serialize=False
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
from django.db import migrations, models
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0003_auto_20150708_1326'),
|
||||
("django_q", "0003_auto_20150708_1326"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='failure',
|
||||
options={'verbose_name_plural': 'Failed tasks', 'verbose_name': 'Failed task', 'ordering': ['-stopped']},
|
||||
name="failure",
|
||||
options={
|
||||
"verbose_name_plural": "Failed tasks",
|
||||
"verbose_name": "Failed task",
|
||||
"ordering": ["-stopped"],
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='success',
|
||||
options={'verbose_name_plural': 'Successful tasks', 'verbose_name': 'Successful task', 'ordering': ['-stopped']},
|
||||
name="success",
|
||||
options={
|
||||
"verbose_name_plural": "Successful tasks",
|
||||
"verbose_name": "Successful task",
|
||||
"ordering": ["-stopped"],
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='task',
|
||||
options={'ordering': ['-stopped']},
|
||||
name="task",
|
||||
options={"ordering": ["-stopped"]},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,18 +4,18 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0004_auto_20150710_1043'),
|
||||
("django_q", "0004_auto_20150710_1043"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='schedule',
|
||||
name='name',
|
||||
model_name="schedule",
|
||||
name="name",
|
||||
field=models.CharField(max_length=100, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='group',
|
||||
model_name="task",
|
||||
name="group",
|
||||
field=models.CharField(max_length=100, null=True, editable=False),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,18 +4,36 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0005_auto_20150718_1506'),
|
||||
("django_q", "0005_auto_20150718_1506"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='schedule',
|
||||
name='minutes',
|
||||
field=models.PositiveSmallIntegerField(help_text='Number of minutes for the Minutes type', blank=True, null=True),
|
||||
model_name="schedule",
|
||||
name="minutes",
|
||||
field=models.PositiveSmallIntegerField(
|
||||
help_text="Number of minutes for the Minutes type",
|
||||
blank=True,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='schedule_type',
|
||||
field=models.CharField(max_length=1, choices=[('O', 'Once'), ('I', 'Minutes'), ('H', 'Hourly'), ('D', 'Daily'), ('W', 'Weekly'), ('M', 'Monthly'), ('Q', 'Quarterly'), ('Y', 'Yearly')], default='O', verbose_name='Schedule Type'),
|
||||
model_name="schedule",
|
||||
name="schedule_type",
|
||||
field=models.CharField(
|
||||
max_length=1,
|
||||
choices=[
|
||||
("O", "Once"),
|
||||
("I", "Minutes"),
|
||||
("H", "Hourly"),
|
||||
("D", "Daily"),
|
||||
("W", "Weekly"),
|
||||
("M", "Monthly"),
|
||||
("Q", "Quarterly"),
|
||||
("Y", "Yearly"),
|
||||
],
|
||||
default="O",
|
||||
verbose_name="Schedule Type",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,21 +4,29 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0006_auto_20150805_1817'),
|
||||
("django_q", "0006_auto_20150805_1817"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OrmQ',
|
||||
name="OrmQ",
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)),
|
||||
('key', models.CharField(max_length=100)),
|
||||
('payload', models.TextField()),
|
||||
('lock', models.DateTimeField(null=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
primary_key=True,
|
||||
auto_created=True,
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("key", models.CharField(max_length=100)),
|
||||
("payload", models.TextField()),
|
||||
("lock", models.DateTimeField(null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name_plural': 'Queued tasks',
|
||||
'verbose_name': 'Queued task',
|
||||
"verbose_name_plural": "Queued tasks",
|
||||
"verbose_name": "Queued task",
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,13 +4,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0007_ormq'),
|
||||
("django_q", "0007_ormq"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='name',
|
||||
model_name="schedule",
|
||||
name="name",
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -4,13 +4,17 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0008_auto_20160224_1026'),
|
||||
("django_q", "0008_auto_20160224_1026"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='repeats',
|
||||
field=models.IntegerField(default=-1, help_text='n = n times, -1 = forever', verbose_name='Repeats'),
|
||||
model_name="schedule",
|
||||
name="repeats",
|
||||
field=models.IntegerField(
|
||||
default=-1,
|
||||
help_text="n = n times, -1 = forever",
|
||||
verbose_name="Repeats",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -5,23 +5,29 @@ from django.db import migrations
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0009_auto_20171009_0915'),
|
||||
("django_q", "0009_auto_20171009_0915"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='args',
|
||||
field=picklefield.fields.PickledObjectField(editable=False, null=True, protocol=-1),
|
||||
model_name="task",
|
||||
name="args",
|
||||
field=picklefield.fields.PickledObjectField(
|
||||
editable=False, null=True, protocol=-1
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='kwargs',
|
||||
field=picklefield.fields.PickledObjectField(editable=False, null=True, protocol=-1),
|
||||
model_name="task",
|
||||
name="kwargs",
|
||||
field=picklefield.fields.PickledObjectField(
|
||||
editable=False, null=True, protocol=-1
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='result',
|
||||
field=picklefield.fields.PickledObjectField(editable=False, null=True, protocol=-1),
|
||||
model_name="task",
|
||||
name="result",
|
||||
field=picklefield.fields.PickledObjectField(
|
||||
editable=False, null=True, protocol=-1
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,18 +6,35 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0010_auto_20200610_0856'),
|
||||
("django_q", "0010_auto_20200610_0856"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='schedule',
|
||||
name='cron',
|
||||
field=models.CharField(blank=True, help_text='Cron expression', max_length=100, null=True),
|
||||
model_name="schedule",
|
||||
name="cron",
|
||||
field=models.CharField(
|
||||
blank=True, help_text="Cron expression", max_length=100, null=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='schedule_type',
|
||||
field=models.CharField(choices=[('O', 'Once'), ('I', 'Minutes'), ('H', 'Hourly'), ('D', 'Daily'), ('W', 'Weekly'), ('M', 'Monthly'), ('Q', 'Quarterly'), ('Y', 'Yearly'), ('C', 'Cron')], default='O', max_length=1, verbose_name='Schedule Type'),
|
||||
model_name="schedule",
|
||||
name="schedule_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("O", "Once"),
|
||||
("I", "Minutes"),
|
||||
("H", "Hourly"),
|
||||
("D", "Daily"),
|
||||
("W", "Weekly"),
|
||||
("M", "Monthly"),
|
||||
("Q", "Quarterly"),
|
||||
("Y", "Yearly"),
|
||||
("C", "Cron"),
|
||||
],
|
||||
default="O",
|
||||
max_length=1,
|
||||
verbose_name="Schedule Type",
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -8,13 +8,19 @@ import django_q.models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0011_auto_20200628_1055'),
|
||||
("django_q", "0011_auto_20200628_1055"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='cron',
|
||||
field=models.CharField(blank=True, help_text='Cron expression', max_length=100, null=True, validators=[django_q.models.validate_cron]),
|
||||
model_name="schedule",
|
||||
name="cron",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text="Cron expression",
|
||||
max_length=100,
|
||||
null=True,
|
||||
validators=[django_q.models.validate_cron],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0012_auto_20200702_1608'),
|
||||
("django_q", "0012_auto_20200702_1608"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='attempt_count',
|
||||
model_name="task",
|
||||
name="attempt_count",
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('django_q', '0013_task_attempt_count'),
|
||||
("django_q", "0013_task_attempt_count"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='schedule',
|
||||
name='cluster',
|
||||
model_name="schedule",
|
||||
name="cluster",
|
||||
field=models.CharField(blank=True, default=None, max_length=100, null=True),
|
||||
),
|
||||
]
|
||||
|
||||
35
django_q/migrations/0015_alter_schedule_schedule_type.py
Normal file
35
django_q/migrations/0015_alter_schedule_schedule_type.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Generated by Django 4.1.2 on 2022-11-10 01:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("django_q", "0014_schedule_cluster"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="schedule",
|
||||
name="schedule_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("O", "Once"),
|
||||
("I", "Minutes"),
|
||||
("H", "Hourly"),
|
||||
("D", "Daily"),
|
||||
("W", "Weekly"),
|
||||
("BW", "Biweekly"),
|
||||
("M", "Monthly"),
|
||||
("BM", "Bimonthly"),
|
||||
("Q", "Quarterly"),
|
||||
("Y", "Yearly"),
|
||||
("C", "Cron"),
|
||||
],
|
||||
default="O",
|
||||
max_length=2,
|
||||
verbose_name="Schedule Type",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -1,3 +1,5 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Django
|
||||
from django import get_version
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -5,6 +7,7 @@ from django.db import models
|
||||
from django.template.defaultfilters import truncatechars
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.timezone import is_aware
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -13,8 +16,10 @@ from picklefield import PickledObjectField
|
||||
from picklefield.fields import dbsafe_decode
|
||||
|
||||
# Local
|
||||
from django_q.conf import croniter
|
||||
from django_q.conf import croniter, Conf
|
||||
from django_q.signing import SignedPackage
|
||||
from django_q.utils import localtime, add_months, add_years
|
||||
|
||||
from .utils import get_func_repr
|
||||
|
||||
|
||||
@@ -166,7 +171,9 @@ class Schedule(models.Model):
|
||||
HOURLY = "H"
|
||||
DAILY = "D"
|
||||
WEEKLY = "W"
|
||||
BIWEEKLY = "BW"
|
||||
MONTHLY = "M"
|
||||
BIMONTHLY = "BM"
|
||||
QUARTERLY = "Q"
|
||||
YEARLY = "Y"
|
||||
CRON = "C"
|
||||
@@ -176,13 +183,15 @@ class Schedule(models.Model):
|
||||
(HOURLY, _("Hourly")),
|
||||
(DAILY, _("Daily")),
|
||||
(WEEKLY, _("Weekly")),
|
||||
(BIWEEKLY, _("Biweekly")),
|
||||
(MONTHLY, _("Monthly")),
|
||||
(BIMONTHLY, _("Bimonthly")),
|
||||
(QUARTERLY, _("Quarterly")),
|
||||
(YEARLY, _("Yearly")),
|
||||
(CRON, _("Cron")),
|
||||
)
|
||||
schedule_type = models.CharField(
|
||||
max_length=1, choices=TYPE, default=TYPE[0][0], verbose_name=_("Schedule Type")
|
||||
max_length=2, choices=TYPE, default=TYPE[0][0], verbose_name=_("Schedule Type")
|
||||
)
|
||||
minutes = models.PositiveSmallIntegerField(
|
||||
null=True, blank=True, help_text=_("Number of minutes for the Minutes type")
|
||||
@@ -203,6 +212,59 @@ class Schedule(models.Model):
|
||||
task = models.CharField(max_length=100, null=True, editable=False)
|
||||
cluster = models.CharField(max_length=100, default=None, null=True, blank=True)
|
||||
|
||||
def calculate_next_run(self, next_run=None):
|
||||
# next run is always in UTC
|
||||
next_run = next_run or self.next_run
|
||||
|
||||
if self.schedule_type == self.CRON:
|
||||
if not croniter:
|
||||
raise ImportError(
|
||||
_("Please install croniter to enable cron expressions")
|
||||
)
|
||||
return croniter(self.cron, localtime()).get_next(datetime)
|
||||
|
||||
if self.schedule_type == self.MINUTES:
|
||||
add = timedelta(minutes=(self.minutes or 1))
|
||||
elif self.schedule_type == self.HOURLY:
|
||||
add = timedelta(hours=1)
|
||||
elif self.schedule_type == self.DAILY:
|
||||
add = timedelta(days=1)
|
||||
elif self.schedule_type == self.WEEKLY:
|
||||
add = timedelta(weeks=1)
|
||||
elif self.schedule_type == self.BIWEEKLY:
|
||||
add = timedelta(weeks=2)
|
||||
elif self.schedule_type == self.MONTHLY:
|
||||
add = timedelta(days=(add_months(next_run, 1) - next_run).days)
|
||||
elif self.schedule_type == self.BIMONTHLY:
|
||||
add = timedelta(days=(add_months(next_run, 2) - next_run).days)
|
||||
elif self.schedule_type == self.QUARTERLY:
|
||||
add = timedelta(days=(add_months(next_run, 3) - next_run).days)
|
||||
elif self.schedule_type == self.YEARLY:
|
||||
add = timedelta(days=(add_years(next_run, 1) - next_run).days)
|
||||
|
||||
# add normal timedelta, we will correct this later based on timezone
|
||||
next_run += add
|
||||
|
||||
# DST differencers don't matter with minutes, hourly or yearly, so skip those
|
||||
if self.schedule_type not in [self.MINUTES, self.HOURLY, self.YEARLY]:
|
||||
# Get localtimes and then remove the tzinfo, so we can get the actual difference
|
||||
current_next_run = localtime(next_run - add).replace(tzinfo=None)
|
||||
new_next_run = localtime(next_run).replace(tzinfo=None)
|
||||
|
||||
# get the difference between them, this should be (-)1 or (-)0.5 hour
|
||||
# based on DST active or not
|
||||
extra_diff = (new_next_run - current_next_run) - add
|
||||
|
||||
# if we have one positive hour difference, then subtract it, so we are even
|
||||
# and vice versa. In most cases, this will be 0, as there won't be a
|
||||
# timezone diff
|
||||
if extra_diff > timedelta(hours=0):
|
||||
next_run -= extra_diff
|
||||
else:
|
||||
next_run += extra_diff
|
||||
|
||||
return next_run
|
||||
|
||||
def success(self):
|
||||
if self.task and Task.objects.filter(id=self.task):
|
||||
return Task.objects.get(id=self.task).success
|
||||
@@ -225,7 +287,6 @@ class Schedule(models.Model):
|
||||
last_run.allow_tags = True
|
||||
last_run.short_description = _("last_run")
|
||||
|
||||
|
||||
class Meta:
|
||||
app_label = "django_q"
|
||||
verbose_name = _("Scheduled task")
|
||||
|
||||
@@ -19,28 +19,30 @@ try:
|
||||
except ImportError:
|
||||
psutil = None
|
||||
|
||||
# optional
|
||||
try:
|
||||
from blessed import Terminal
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def get_process_mb(pid):
|
||||
try:
|
||||
process = psutil.Process(pid)
|
||||
mb_used = round(process.memory_info().rss / 1024 ** 2, 2)
|
||||
mb_used = round(process.memory_info().rss / 1024**2, 2)
|
||||
except psutil.NoSuchProcess:
|
||||
mb_used = "NO_PROCESS_FOUND"
|
||||
return mb_used
|
||||
|
||||
BLESSED_INSTALL_MESSAGE = "Blessed is not installed. Please install blessed to use this: https://pypi.org/project/blessed/"
|
||||
|
||||
BLESSED_INSTALL_MESSAGE = (
|
||||
"Blessed is not installed. Please install blessed to use this: "
|
||||
"https://pypi.org/project/blessed/"
|
||||
)
|
||||
|
||||
|
||||
def monitor(run_once=False, broker=None):
|
||||
if not broker:
|
||||
broker = get_broker()
|
||||
try:
|
||||
from blessed import Terminal
|
||||
|
||||
term = Terminal()
|
||||
except:
|
||||
except ImportError:
|
||||
print(BLESSED_INSTALL_MESSAGE)
|
||||
return
|
||||
|
||||
@@ -204,8 +206,10 @@ def info(broker=None):
|
||||
if not broker:
|
||||
broker = get_broker()
|
||||
try:
|
||||
from blessed import Terminal
|
||||
|
||||
term = Terminal()
|
||||
except:
|
||||
except ImportError:
|
||||
print(BLESSED_INSTALL_MESSAGE)
|
||||
return
|
||||
|
||||
@@ -256,9 +260,12 @@ def info(broker=None):
|
||||
print(
|
||||
term.black_on_green(
|
||||
term.center(
|
||||
_(
|
||||
f'-- {Conf.PREFIX.capitalize()} { ".".join(str(v) for v in VERSION)} on {broker.info()} --'
|
||||
)
|
||||
_("-- %(prefix)s %(version)s on %(info)s --")
|
||||
% {
|
||||
"prefix": Conf.PREFIX.capitalize(),
|
||||
"version": ".".join(str(v) for v in VERSION),
|
||||
"info": broker.info(),
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -293,7 +300,7 @@ def info(broker=None):
|
||||
+ term.move_x(1 * col_width)
|
||||
+ term.white(str(models.Schedule.objects.count()))
|
||||
+ term.move_x(2 * col_width)
|
||||
+ term.cyan(_(f"Tasks/{per}"))
|
||||
+ term.cyan(_("Tasks/%(per)s") % {"per": per})
|
||||
+ term.move_x(3 * col_width)
|
||||
+ term.white(f"{tasks_per:.2f}")
|
||||
+ term.move_x(4 * col_width)
|
||||
@@ -308,8 +315,10 @@ def memory(run_once=False, workers=False, broker=None):
|
||||
if not broker:
|
||||
broker = get_broker()
|
||||
try:
|
||||
from blessed import Terminal
|
||||
|
||||
term = Terminal()
|
||||
except:
|
||||
except ImportError:
|
||||
print(BLESSED_INSTALL_MESSAGE)
|
||||
return
|
||||
broker.ping()
|
||||
@@ -389,7 +398,7 @@ def memory(run_once=False, workers=False, broker=None):
|
||||
)
|
||||
# memory available (MB)
|
||||
memory_available = round(
|
||||
psutil.virtual_memory().available / 1024 ** 2, 2
|
||||
psutil.virtual_memory().available / 1024**2, 2
|
||||
)
|
||||
if memory_available_percentage < MEMORY_AVAILABLE_LOWEST_PERCENTAGE:
|
||||
MEMORY_AVAILABLE_LOWEST_PERCENTAGE = memory_available_percentage
|
||||
@@ -413,7 +422,7 @@ def memory(run_once=False, workers=False, broker=None):
|
||||
print(
|
||||
term.move(row, 4 * col_width)
|
||||
+ term.center(
|
||||
round(psutil.virtual_memory().total / 1024 ** 2, 2),
|
||||
round(psutil.virtual_memory().total / 1024**2, 2),
|
||||
width=col_width - 1,
|
||||
)
|
||||
)
|
||||
@@ -475,17 +484,18 @@ def memory(run_once=False, workers=False, broker=None):
|
||||
row += 1
|
||||
print(
|
||||
term.move(row, 0)
|
||||
+ _("Available lowest (%): {} ({})").format(
|
||||
str(MEMORY_AVAILABLE_LOWEST_PERCENTAGE),
|
||||
MEMORY_AVAILABLE_LOWEST_PERCENTAGE_AT.strftime(
|
||||
+ _("Available lowest (): %(memory_percent)s ((at)s)")
|
||||
% {
|
||||
"memory_percent": str(MEMORY_AVAILABLE_LOWEST_PERCENTAGE),
|
||||
"at": MEMORY_AVAILABLE_LOWEST_PERCENTAGE_AT.strftime(
|
||||
"%Y-%m-%d %H:%M:%S+00:00"
|
||||
),
|
||||
)
|
||||
}
|
||||
)
|
||||
# for testing
|
||||
if run_once:
|
||||
return Stat.get_all(broker=broker)
|
||||
print(term.move(row + 2, 0) + term.center("[Press q to quit]"))
|
||||
print(term.move(row + 2, 0) + term.center(_("[Press q to quit]")))
|
||||
val = term.inkey(timeout=1)
|
||||
|
||||
|
||||
@@ -496,5 +506,5 @@ def get_ids():
|
||||
for s in stat:
|
||||
print(s.cluster_id)
|
||||
else:
|
||||
print("No clusters appear to be running.")
|
||||
print(_("No clusters appear to be running."))
|
||||
return True
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""
|
||||
The code is derived from https://github.com/althonos/pronto/commit/3384010dfb4fc7c66a219f59276adef3288a886b
|
||||
The code is derived from
|
||||
https://github.com/althonos/pronto/commit/3384010dfb4fc7c66a219f59276adef3288a886b
|
||||
"""
|
||||
import multiprocessing
|
||||
import multiprocessing.queues
|
||||
|
||||
@@ -19,16 +19,16 @@ def call_hook(sender, instance, **kwargs):
|
||||
f = getattr(m, func)
|
||||
except (ValueError, ImportError, AttributeError):
|
||||
logger.error(
|
||||
_(f"malformed return hook '{instance.hook}' for [{instance.name}]")
|
||||
_("malformed return hook '%(hook)s' for [%(name)s]")
|
||||
% {"hook": instance.hook, "name": instance.name}
|
||||
)
|
||||
return
|
||||
try:
|
||||
f(instance)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
_(
|
||||
f"return hook {instance.hook} failed on [{instance.name}] because {str(e)}"
|
||||
)
|
||||
_("return hook %(hook)s failed on [%(name)s] because %(error)s")
|
||||
% {"hook": instance.hook, "name": instance.name, "error": str(e)}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -600,7 +600,8 @@ class Chain:
|
||||
|
||||
def result(self, wait=0):
|
||||
"""
|
||||
return the full list of results from the chain when it finishes. blocks until timeout.
|
||||
return the full list of results from the chain when it finishes. blocks until
|
||||
timeout.
|
||||
:param int wait: how many milliseconds to wait for a result
|
||||
:return: an unsorted list of results
|
||||
"""
|
||||
@@ -611,7 +612,8 @@ class Chain:
|
||||
|
||||
def fetch(self, failures=True, wait=0):
|
||||
"""
|
||||
get the task result objects from the chain when it finishes. blocks until timeout.
|
||||
get the task result objects from the chain when it finishes. blocks until
|
||||
timeout.
|
||||
:param failures: include failed tasks
|
||||
:param int wait: how many milliseconds to wait for a result
|
||||
:return: an unsorted list of task objects
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import os
|
||||
|
||||
import django
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
@@ -77,7 +75,7 @@ DATABASES = {
|
||||
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
TIME_ZONE = "Europe/Amsterdam"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
@@ -130,5 +128,5 @@ Q_CLUSTER = {
|
||||
"testing": True,
|
||||
"log_level": "DEBUG",
|
||||
"django_redis": "default",
|
||||
"redis": f"redis://{REDIS_HOST}:6379/0"
|
||||
"redis": f"redis://{REDIS_HOST}:6379/0",
|
||||
}
|
||||
|
||||
@@ -2,12 +2,11 @@ import os
|
||||
from time import sleep
|
||||
|
||||
import pytest
|
||||
import redis
|
||||
|
||||
from django_q.brokers import Broker, get_broker
|
||||
from django_q.conf import Conf
|
||||
from django_q.humanhash import uuid
|
||||
from django_q.tests.settings import REDIS_HOST, MONGO_HOST
|
||||
from django_q.tests.settings import MONGO_HOST, REDIS_HOST
|
||||
|
||||
|
||||
def test_broker(monkeypatch):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import uuid as uuidlib
|
||||
from datetime import datetime
|
||||
from math import copysign
|
||||
from multiprocessing import Event, Value
|
||||
from time import sleep
|
||||
@@ -11,9 +11,6 @@ from typing import Optional
|
||||
import pytest
|
||||
from django.utils import timezone
|
||||
|
||||
myPath = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, myPath + "/../")
|
||||
|
||||
from django_q.brokers import Broker, get_broker
|
||||
from django_q.cluster import Cluster, Sentinel, monitor, pusher, save_task, worker
|
||||
from django_q.conf import Conf
|
||||
@@ -32,9 +29,12 @@ from django_q.tasks import (
|
||||
result,
|
||||
result_group,
|
||||
)
|
||||
from django_q.tests.tasks import TaskError, multiply
|
||||
from django_q.tests.tasks import multiply, TaskError
|
||||
from django_q.utils import add_months, add_years
|
||||
|
||||
myPath = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, myPath + "/../")
|
||||
|
||||
|
||||
class WordClass:
|
||||
def __init__(self):
|
||||
@@ -64,7 +64,7 @@ def test_sync(broker):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_sync_raise_exception(broker):
|
||||
with pytest.raises(Exception):
|
||||
with pytest.raises(TaskError):
|
||||
async_task("django_q.tests.tasks.raise_exception", broker=broker, sync=True)
|
||||
|
||||
|
||||
@@ -409,6 +409,7 @@ def test_recycle(broker, monkeypatch):
|
||||
assert Success.objects.count() == Conf.SAVE_LIMIT
|
||||
broker.delete_queue()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_save_limit_per_func(broker, monkeypatch):
|
||||
# set up the Sentinel
|
||||
@@ -442,15 +443,14 @@ def test_save_limit_per_func(broker, monkeypatch):
|
||||
# run monitor
|
||||
monitor(result_queue)
|
||||
assert Success.objects.count() == 3
|
||||
assert set(Success.objects.filter().values_list('func', flat=True)) == {
|
||||
'django_q.tests.tasks.countdown',
|
||||
'django_q.tests.tasks.hello',
|
||||
'django_q.tests.tasks.multiply',
|
||||
assert set(Success.objects.filter().values_list("func", flat=True)) == {
|
||||
"django_q.tests.tasks.countdown",
|
||||
"django_q.tests.tasks.hello",
|
||||
"django_q.tests.tasks.multiply",
|
||||
}
|
||||
broker.delete_queue()
|
||||
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_max_rss(broker, monkeypatch):
|
||||
# set up the Sentinel
|
||||
@@ -538,7 +538,6 @@ def test_attempt_count(broker, monkeypatch):
|
||||
assert saved_task.attempt_count == 1
|
||||
sleep(0.5)
|
||||
# second save
|
||||
old_stopped = task["stopped"]
|
||||
task["stopped"] = timezone.now()
|
||||
save_task(task, broker)
|
||||
saved_task = Task.objects.get(id=task["id"])
|
||||
@@ -770,6 +769,7 @@ def test_add_months():
|
||||
assert new_date.month == 2
|
||||
assert new_date.day == 29
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_add_years():
|
||||
# add some months
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
from multiprocessing import Event, Value
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import django
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError
|
||||
from django.test import override_settings
|
||||
@@ -11,7 +12,7 @@ from django.utils import timezone
|
||||
from django.utils.timezone import is_naive
|
||||
|
||||
from django_q.brokers import Broker, get_broker
|
||||
from django_q.cluster import monitor, pusher, scheduler, worker, localtime
|
||||
from django_q.cluster import localtime, monitor, pusher, scheduler, worker
|
||||
from django_q.conf import Conf
|
||||
from django_q.queues import Queue
|
||||
from django_q.tasks import Schedule, fetch
|
||||
@@ -21,11 +22,24 @@ from django_q.tests.testing_utilities.multiple_database_routers import (
|
||||
TestingMultipleAppsDatabaseRouter,
|
||||
TestingReplicaDatabaseRouter,
|
||||
)
|
||||
from django_q.utils import add_months
|
||||
|
||||
if django.VERSION < (4, 0):
|
||||
# pytz is the default in django 3.2. Remove when no support for 3.2
|
||||
from pytz import timezone as ZoneInfo
|
||||
else:
|
||||
try:
|
||||
from zoneinfo import ZoneInfo
|
||||
except ImportError:
|
||||
from backports.zoneinfo import ZoneInfo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def broker(monkeypatch) -> Broker:
|
||||
"""Patches the Conf object setting the DJANGO_REDIS attribute allowing a default redis configuration."""
|
||||
"""
|
||||
Patches the Conf object setting the DJANGO_REDIS attribute allowing a default
|
||||
redis configuration.
|
||||
"""
|
||||
monkeypatch.setattr(Conf, "DJANGO_REDIS", "default")
|
||||
return get_broker()
|
||||
|
||||
@@ -65,7 +79,7 @@ REPLICA_DATABASES = {
|
||||
}
|
||||
|
||||
MULTIPLE_APPS_DATABASE_ROUTERS = [
|
||||
f"{TestingMultipleAppsDatabaseRouter.__module__}.{TestingMultipleAppsDatabaseRouter.__name__}"
|
||||
f"{TestingMultipleAppsDatabaseRouter.__module__}.{TestingMultipleAppsDatabaseRouter.__name__}" # noqa: E501
|
||||
]
|
||||
MULTIPLE_APPS_DATABASES = {
|
||||
"default": {
|
||||
@@ -79,6 +93,63 @@ MULTIPLE_APPS_DATABASES = {
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_scheduler_daylight_saving_time_daily(broker, monkeypatch):
|
||||
# Set up a startdate in the Amsterdam timezone (without dst 1 hour ahead). The
|
||||
# 28th of March 2021 is the day when sunlight saving starts (at 2 am)
|
||||
|
||||
monkeypatch.setattr(Conf, "TIME_ZONE", "Europe/Amsterdam")
|
||||
tz = ZoneInfo('Europe/Amsterdam')
|
||||
broker.list_key = "scheduler_test:q"
|
||||
# Let's start a schedule at 1 am on the 27th of March. This is in AMS timezone.
|
||||
# So, 2021-03-27 00:00:00 when saved (due to TZ being Amsterdam and saved in UTC)
|
||||
start_date = datetime(2021, 3, 27, 1, 0, 0)
|
||||
|
||||
# Create schedule with the next run date on the start date. It will move one day
|
||||
# forward when we run the scheduler
|
||||
schedule = create_schedule(
|
||||
"math.copysign",
|
||||
1,
|
||||
-1,
|
||||
name="test math",
|
||||
schedule_type=Schedule.DAILY,
|
||||
next_run=start_date,
|
||||
)
|
||||
|
||||
# Run scheduler so we get the next run date
|
||||
scheduler(broker=broker)
|
||||
schedule.refresh_from_db()
|
||||
|
||||
# It's now the day after exactly at midnight UTC
|
||||
next_run = schedule.next_run
|
||||
assert str(next_run) == "2021-03-28 00:00:00+00:00"
|
||||
|
||||
# In the Amsterdam timezone, it's 1 hour over midnight (+01)
|
||||
next_run = next_run.astimezone(tz)
|
||||
assert str(next_run) == "2021-03-28 01:00:00+01:00"
|
||||
|
||||
# Run scheduler so we get the next run date
|
||||
scheduler(broker=broker)
|
||||
schedule.refresh_from_db()
|
||||
|
||||
next_run = schedule.next_run
|
||||
|
||||
assert str(next_run) == "2021-03-28 23:00:00+00:00"
|
||||
next_run = next_run.astimezone(tz)
|
||||
# In the Amsterdam timezone, it's 1 hour over midnight (+02)
|
||||
assert str(next_run) == "2021-03-29 01:00:00+02:00"
|
||||
|
||||
# Run scheduler so we get the next run date
|
||||
scheduler(broker=broker)
|
||||
schedule.refresh_from_db()
|
||||
|
||||
next_run = schedule.next_run
|
||||
|
||||
assert str(next_run) == "2021-03-29 23:00:00+00:00"
|
||||
next_run = next_run.astimezone(tz)
|
||||
assert str(next_run) == "2021-03-30 01:00:00+02:00"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_scheduler(broker, monkeypatch):
|
||||
broker.list_key = "scheduler_test:q"
|
||||
@@ -228,6 +299,29 @@ def test_scheduler(broker, monkeypatch):
|
||||
# Done
|
||||
broker.delete_queue()
|
||||
|
||||
# test bimonthly
|
||||
schedule = create_schedule(
|
||||
"django_q.tests.tasks.word_multiply",
|
||||
2,
|
||||
word="catch_up",
|
||||
schedule_type=Schedule.BIMONTHLY,
|
||||
)
|
||||
scheduler(broker=broker)
|
||||
schedule = Schedule.objects.get(pk=schedule.pk)
|
||||
assert schedule.next_run.date() == add_months(timezone.now(), 2).date()
|
||||
|
||||
# test biweekly
|
||||
schedule = create_schedule(
|
||||
"django_q.tests.tasks.word_multiply",
|
||||
2,
|
||||
word="catch_up",
|
||||
schedule_type=Schedule.BIWEEKLY,
|
||||
)
|
||||
scheduler(broker=broker)
|
||||
schedule = Schedule.objects.get(pk=schedule.pk)
|
||||
assert schedule.next_run.date() == (timezone.now() + timedelta(weeks=2)).date()
|
||||
broker.delete_queue()
|
||||
|
||||
monkeypatch.setattr(Conf, "PREFIX", "some_cluster_name")
|
||||
# create a schedule on another cluster
|
||||
schedule = create_schedule(
|
||||
@@ -287,7 +381,8 @@ def test_scheduler_atomic_transaction_must_specify_a_database_when_no_replicas_a
|
||||
"""
|
||||
GIVEN a environment without a read replica database
|
||||
WHEN the scheduler is called
|
||||
THEN the transaction atomic must be called using the configured database in the Conf.ORM settings.
|
||||
THEN the transaction atomic must be called using the configured database in the
|
||||
Conf.ORM settings.
|
||||
"""
|
||||
broker = orm_no_replica_broker
|
||||
with mock.patch("django_q.cluster.db") as mocked_db:
|
||||
@@ -300,13 +395,14 @@ def test_scheduler_atomic_transaction_must_specify_a_database_when_no_replicas_a
|
||||
DATABASE_ROUTERS=REPLICA_DATABASE_ROUTERS, DATABASES=REPLICA_DATABASES
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_scheduler_atomic_transaction_must_specify_no_database_when_read_write_replicas_are_used(
|
||||
def test_scheduler_atomic_must_specify_no_db_when_read_write_replicas_are_used(
|
||||
orm_replica_broker: Broker,
|
||||
):
|
||||
"""
|
||||
GIVEN a environment with a read/write configured replica database
|
||||
WHEN the scheduler is called
|
||||
THEN the transaction must be called without a specific database, thus letting the database router pick.
|
||||
THEN the transaction must be called without a specific database, thus letting the
|
||||
database router pick.
|
||||
"""
|
||||
with mock.patch("django_q.cluster.db") as mocked_db:
|
||||
scheduler(broker=orm_replica_broker)
|
||||
@@ -318,13 +414,14 @@ def test_scheduler_atomic_transaction_must_specify_no_database_when_read_write_r
|
||||
DATABASE_ROUTERS=MULTIPLE_APPS_DATABASE_ROUTERS, DATABASES=MULTIPLE_APPS_DATABASES
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_scheduler_atomic_transaction_must_specify_the_database_based_on_router_redirection(
|
||||
def test_scheduler_atomic_must_specify_the_database_based_on_router_redirection(
|
||||
orm_no_replica_broker: Broker,
|
||||
):
|
||||
"""
|
||||
GIVEN a environment without a read replica database
|
||||
WHEN the scheduler is called
|
||||
THEN the transaction atomic must be called using the configured database in the Conf.ORM settings.
|
||||
THEN the transaction atomic must be called using the configured database in the
|
||||
Conf.ORM settings.
|
||||
"""
|
||||
broker = orm_no_replica_broker
|
||||
with mock.patch("django_q.cluster.db") as mocked_db:
|
||||
|
||||
@@ -1,6 +1,23 @@
|
||||
from datetime import datetime
|
||||
import calendar
|
||||
import inspect
|
||||
from datetime import date
|
||||
import calendar
|
||||
|
||||
import django
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
||||
from django_q.conf import Conf
|
||||
|
||||
if django.VERSION < (4, 0):
|
||||
# pytz is the default in django 3.2. Remove when no support for 3.2
|
||||
from pytz import timezone as ZoneInfo
|
||||
else:
|
||||
try:
|
||||
from zoneinfo import ZoneInfo
|
||||
except ImportError:
|
||||
from backports.zoneinfo import ZoneInfo
|
||||
|
||||
|
||||
# credits: https://stackoverflow.com/a/4131114
|
||||
# Made them aware of timezone
|
||||
@@ -8,21 +25,21 @@ def add_months(d, months):
|
||||
month = d.month - 1 + months
|
||||
year = d.year + month // 12
|
||||
month = month % 12 + 1
|
||||
day = min(d.day, calendar.monthrange(year,month)[1])
|
||||
day = min(d.day, calendar.monthrange(year, month)[1])
|
||||
return d.replace(year=year, month=month, day=day)
|
||||
|
||||
|
||||
# credits: https://stackoverflow.com/a/15743908
|
||||
# Changed the last line to make it a little easier to read and changed it to move February 29 to 28 next year
|
||||
# Also made them aware of timezone
|
||||
# Changed the last line to make it a little easier to read and changed it to move
|
||||
# February 29 to 28 next year.
|
||||
def add_years(d, years):
|
||||
"""Return a date that's `years` years after the date (or datetime)
|
||||
object `d`. Return the same calendar date (month and day) in the
|
||||
destination year, if it exists, otherwise use the previous day
|
||||
(thus changing February 29 to February 28).
|
||||
|
||||
"""
|
||||
try:
|
||||
return d.replace(year = d.year + years)
|
||||
return d.replace(year=d.year + years)
|
||||
except ValueError:
|
||||
new_date = d + (date(d.year + years, 3, 1) - date(d.year, 3, 1))
|
||||
return d.replace(year=new_date.year, month=new_date.month, day=new_date.day)
|
||||
@@ -32,11 +49,26 @@ def get_func_repr(func):
|
||||
# convert func to string
|
||||
if inspect.isfunction(func):
|
||||
return f"{func.__module__}.{func.__name__}"
|
||||
elif inspect.ismethod(func) and hasattr(func.__self__, '__name__'):
|
||||
elif inspect.ismethod(func) and hasattr(func.__self__, "__name__"):
|
||||
return (
|
||||
f"{func.__self__.__module__}."
|
||||
f"{func.__self__.__name__}.{func.__name__}"
|
||||
f"{func.__self__.__module__}." f"{func.__self__.__name__}.{func.__name__}"
|
||||
)
|
||||
else:
|
||||
return str(func)
|
||||
|
||||
|
||||
def localtime(value=None) -> datetime:
|
||||
"""Override for timezone.localtime to deal with naive times and local times"""
|
||||
if settings.USE_TZ:
|
||||
if django.VERSION >= (4, 0) and settings.USE_DEPRECATED_PYTZ:
|
||||
import pytz
|
||||
|
||||
convert_to_tz = pytz.timezone(Conf.TIME_ZONE)
|
||||
else:
|
||||
convert_to_tz = ZoneInfo(Conf.TIME_ZONE)
|
||||
|
||||
return timezone.localtime(value=value, timezone=convert_to_tz)
|
||||
if value is None:
|
||||
return datetime.now()
|
||||
else:
|
||||
return value
|
||||
|
||||
87
docs/conf.py
87
docs/conf.py
@@ -16,12 +16,10 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
import sphinx_rtd_theme
|
||||
|
||||
myPath = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, myPath + '/../')
|
||||
os.environ['DJANGO_SETTINGS_MODULE'] = 'django_q.tests.settings'
|
||||
nitpick_ignore = [('py:class', 'datetime')]
|
||||
sys.path.insert(0, myPath + "/../")
|
||||
os.environ["DJANGO_SETTINGS_MODULE"] = "django_q.tests.settings"
|
||||
nitpick_ignore = [("py:class", "datetime")]
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
@@ -37,50 +35,54 @@ nitpick_ignore = [('py:class', 'datetime')]
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx_rtd_theme',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.intersphinx',
|
||||
"sphinx_rtd_theme",
|
||||
"sphinx.ext.todo",
|
||||
"sphinx.ext.intersphinx",
|
||||
# 'sphinx.ext.autodoc'
|
||||
]
|
||||
|
||||
intersphinx_mapping = {'python': ('https://docs.python.org/3.8', None),
|
||||
'django': ('https://docs.djangoproject.com/en/2.2/',
|
||||
'https://docs.djangoproject.com/en/2.2/_objects/')}
|
||||
intersphinx_mapping = {
|
||||
"python": ("https://docs.python.org/3.8", None),
|
||||
"django": (
|
||||
"https://docs.djangoproject.com/en/2.2/",
|
||||
"https://docs.djangoproject.com/en/2.2/_objects/",
|
||||
),
|
||||
}
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
templates_path = ["_templates"]
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
source_suffix = ".rst"
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = 'Django Q2'
|
||||
copyright = '2015-2021, Ilan Steemers - 2022, Stan Triepels'
|
||||
author = 'Ilan Steemers, Stan Triepels'
|
||||
project = "Django Q2"
|
||||
copyright = "2015-2021, Ilan Steemers - 2022, Stan Triepels"
|
||||
author = "Ilan Steemers, Stan Triepels"
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '1.4'
|
||||
version = "1.4"
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.4.3'
|
||||
release = "1.4.8"
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = 'en'
|
||||
language = "en"
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
@@ -90,7 +92,7 @@ language = 'en'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
exclude_patterns = ["_build"]
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
@@ -108,7 +110,7 @@ add_module_names = False
|
||||
# show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
pygments_style = "sphinx"
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
@@ -124,7 +126,7 @@ todo_include_todos = True
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
@@ -136,11 +138,11 @@ html_theme_options = {
|
||||
# 'github_banner': True,
|
||||
}
|
||||
html_sidebars = {
|
||||
'**': [
|
||||
'about.html',
|
||||
'navigation.html',
|
||||
'relations.html',
|
||||
'searchbox.html',
|
||||
"**": [
|
||||
"about.html",
|
||||
"navigation.html",
|
||||
"relations.html",
|
||||
"searchbox.html",
|
||||
]
|
||||
}
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
@@ -161,12 +163,12 @@ html_sidebars = {
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
html_favicon = '_static/favicon.ico'
|
||||
html_favicon = "_static/favicon.ico"
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
@@ -229,20 +231,17 @@ html_static_path = ['_static']
|
||||
# html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'DjangoQ2doc'
|
||||
htmlhelp_basename = "DjangoQ2doc"
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
@@ -251,8 +250,7 @@ latex_elements = {
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'DjangoQ2.tex', 'Django Q2 Documentation',
|
||||
'Ilan Steemers', 'manual'),
|
||||
(master_doc, "DjangoQ2.tex", "Django Q2 Documentation", "Ilan Steemers", "manual"),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
@@ -280,10 +278,7 @@ latex_documents = [
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'djangoq2', 'Django Q2 Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
man_pages = [(master_doc, "djangoq2", "Django Q2 Documentation", [author], 1)]
|
||||
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
@@ -296,9 +291,15 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'DjangoQ2', 'Django Q2 Documentation',
|
||||
author, 'DjangoQ2', 'A multiprocessing distributed task queue for Django.',
|
||||
'Miscellaneous'),
|
||||
(
|
||||
master_doc,
|
||||
"DjangoQ2",
|
||||
"Django Q2 Documentation",
|
||||
author,
|
||||
"DjangoQ2",
|
||||
"A multiprocessing distributed task queue for Django.",
|
||||
"Miscellaneous",
|
||||
),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
|
||||
@@ -70,6 +70,13 @@ Set this to something that makes sense for your project. Can be overridden for i
|
||||
|
||||
See :ref:`retry` for details how to set values for timeout and retry.
|
||||
|
||||
.. _time_zone:
|
||||
|
||||
time_zone
|
||||
~~~~~~~
|
||||
|
||||
The timezone that is used for task scheduling. Use this if you are having issue with DST. The scheduler uses UTC to calculate the next date and will therefore ignore any DST changes. This will cause 1 hour or 0.5 hour changes in the schedule when time is moved one hour ahead or back. Defaults to `settings.TIME_ZONE` if `USE_TZ` is enabled.
|
||||
|
||||
.. _ack_failures:
|
||||
|
||||
ack_failures
|
||||
|
||||
@@ -165,7 +165,7 @@ Reference
|
||||
|
||||
.. py:attribute:: TYPE
|
||||
|
||||
:attr:`ONCE`, :attr:`MINUTES`, :attr:`HOURLY`, :attr:`DAILY`, :attr:`WEEKLY`, :attr:`MONTHLY`, :attr:`QUARTERLY`, :attr:`YEARLY`, :attr:`CRON`
|
||||
:attr:`ONCE`, :attr:`MINUTES`, :attr:`HOURLY`, :attr:`DAILY`, :attr:`WEEKLY`, :attr:`BIWEEKLY`, :attr:`MONTHLY`, :attr:`BIMONTHLY`, :attr:`QUARTERLY`, :attr:`YEARLY`, :attr:`CRON`
|
||||
|
||||
|
||||
.. py:attribute:: minutes
|
||||
@@ -224,10 +224,23 @@ Reference
|
||||
|
||||
`'W'` the task will run every week on they day and time of the first run.
|
||||
|
||||
.. py:attribute:: BIWEEKLY
|
||||
|
||||
`'BW'` the task will run once every two weeks on they day and time of the first run.
|
||||
|
||||
.. py:attribute:: MONTHLY
|
||||
|
||||
`'M'` the tasks runs every month on they day and time of the last run.
|
||||
|
||||
.. note::
|
||||
|
||||
Months are tricky. If you schedule something on the 31st of the month and the next month has only 30 days or less, the task will run on the last day of the next month.
|
||||
It will however continue to run on that day, e.g. the 28th, in subsequent months.
|
||||
|
||||
.. py:attribute:: BIMONTHLY
|
||||
|
||||
`'BM'` the tasks runs once every two months on they day and time of the last run.
|
||||
|
||||
.. note::
|
||||
|
||||
Months are tricky. If you schedule something on the 31st of the month and the next month has only 30 days or less, the task will run on the last day of the next month.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "django-q2"
|
||||
version = "1.4.3"
|
||||
version = "1.4.8"
|
||||
packages = [
|
||||
{ include = "django_q" },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user