From 5c5a2622d0dd1d00eb99dda0b4de71d157341e42 Mon Sep 17 00:00:00 2001 From: dreddit Date: Sat, 27 Feb 2010 19:02:08 +0100 Subject: [PATCH] Added django_cron --- django_cron/.svn/all-wcprops | 35 ++++ django_cron/.svn/entries | 198 ++++++++++++++++++ django_cron/.svn/format | 1 + .../.svn/text-base/README.txt.svn-base | 54 +++++ .../.svn/text-base/__init__.py.svn-base | 63 ++++++ django_cron/.svn/text-base/base.py.svn-base | 127 +++++++++++ django_cron/.svn/text-base/models.py.svn-base | 39 ++++ .../.svn/text-base/signals.py.svn-base | 26 +++ django_cron/README.txt | 54 +++++ django_cron/__init__.py | 63 ++++++ django_cron/base.py | 127 +++++++++++ django_cron/models.py | 39 ++++ django_cron/signals.py | 26 +++ settings.py | 3 +- 14 files changed, 854 insertions(+), 1 deletion(-) create mode 100644 django_cron/.svn/all-wcprops create mode 100644 django_cron/.svn/entries create mode 100644 django_cron/.svn/format create mode 100644 django_cron/.svn/text-base/README.txt.svn-base create mode 100644 django_cron/.svn/text-base/__init__.py.svn-base create mode 100644 django_cron/.svn/text-base/base.py.svn-base create mode 100644 django_cron/.svn/text-base/models.py.svn-base create mode 100644 django_cron/.svn/text-base/signals.py.svn-base create mode 100644 django_cron/README.txt create mode 100644 django_cron/__init__.py create mode 100644 django_cron/base.py create mode 100644 django_cron/models.py create mode 100644 django_cron/signals.py diff --git a/django_cron/.svn/all-wcprops b/django_cron/.svn/all-wcprops new file mode 100644 index 0000000..0dc4d5b --- /dev/null +++ b/django_cron/.svn/all-wcprops @@ -0,0 +1,35 @@ +K 25 +svn:wc:ra_dav:version-url +V 34 +/svn/!svn/ver/32/trunk/django_cron +END +base.py +K 25 +svn:wc:ra_dav:version-url +V 42 +/svn/!svn/ver/32/trunk/django_cron/base.py +END +__init__.py +K 25 +svn:wc:ra_dav:version-url +V 46 +/svn/!svn/ver/16/trunk/django_cron/__init__.py +END +signals.py +K 25 +svn:wc:ra_dav:version-url +V 44 +/svn/!svn/ver/8/trunk/django_cron/signals.py +END +models.py +K 25 +svn:wc:ra_dav:version-url +V 44 +/svn/!svn/ver/16/trunk/django_cron/models.py +END +README.txt +K 25 +svn:wc:ra_dav:version-url +V 45 +/svn/!svn/ver/16/trunk/django_cron/README.txt +END diff --git a/django_cron/.svn/entries b/django_cron/.svn/entries new file mode 100644 index 0000000..77efd1a --- /dev/null +++ b/django_cron/.svn/entries @@ -0,0 +1,198 @@ +9 + +dir +32 +http://django-cron.googlecode.com/svn/trunk/django_cron +http://django-cron.googlecode.com/svn + + + +2009-10-28T14:21:55.477932Z +32 +jim.mixtake@gmail.com + + +svn:special svn:externals svn:needs-lock + + + + + + + + + + + +857153d3-df44-0410-aaef-ab1f86cdcd94 + +base.py +file + + + + +2010-02-20T20:20:49.000000Z +afab29654c6380b9c2ecd00fae92522c +2009-10-28T14:21:55.477932Z +32 +jim.mixtake@gmail.com + + + + + + + + + + + + + + + + + + + + + +4064 + +__init__.py +file + + + + +2010-02-20T20:20:49.000000Z +8492cf50017e7eff784fdfbe0cc8b3ba +2009-06-02T13:59:46.682354Z +16 +Jim.mixtake + + + + + + + + + + + + + + + + + + + + + +2566 + +signals.py +file + + + + +2010-02-20T20:20:49.000000Z +8e3eba63f720bd6d830fc89d7d6be693 +2008-10-26T12:02:48.705921Z +5 +digitalxero + + + + + + + + + + + + + + + + + + + + + +1192 + +models.py +file + + + + +2010-02-20T20:20:49.000000Z +329f05f78f78465d4acfba95c42d32f7 +2009-06-02T13:59:46.682354Z +16 +Jim.mixtake + + + + + + + + + + + + + + + + + + + + + +1632 + +README.txt +file + + + + +2010-02-20T20:20:49.000000Z +801836776103697e20d5d9a9446cb28b +2009-06-02T13:59:46.682354Z +16 +Jim.mixtake + + + + + + + + + + + + + + + + + + + + + +1953 + diff --git a/django_cron/.svn/format b/django_cron/.svn/format new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/django_cron/.svn/format @@ -0,0 +1 @@ +9 diff --git a/django_cron/.svn/text-base/README.txt.svn-base b/django_cron/.svn/text-base/README.txt.svn-base new file mode 100644 index 0000000..cb0320b --- /dev/null +++ b/django_cron/.svn/text-base/README.txt.svn-base @@ -0,0 +1,54 @@ += How to install djang-cron = + +1. Put 'django_cron' into your python path + +2. Add 'django_cron' to INSTALLED_APPS in your settings.py file + +3. Add the following code to the beginning of your urls.py file (just after the imports): + + import django_cron + django_cron.autodiscover() + + +4. Create a file called 'cron.py' inside each installed app that you want to add a recurring job to. The app must be installed via the INSTALLED_APPS in your settings.py or the autodiscover will not find it. + +=== Important note === + +If you are using mod_python, you need to make sure your server is set up to server more than one request per instance, Otherwise it will kill django-cron before the tasks get started. The specific line to look for is in your 'httpd.conf' file: + + + # THIS IS BAD!!! IT WILL CRIPPLE DJANGO-CRON + MaxRequestsPerChild 1 + + +Change it to a value that is large enough that your cron jobs will get run at least once per instance. We're working on resolving this issue without dictating your server config. + +In the meantime, django_cron is best used to execute tasks that occur relatively often (at least once an hour). Try setting MaxRequestsPerChild to 50, 100, or 200 + + # Depending on traffic, and your server config, a number between 50 and 500 is probably good + # Note: the higher this number, the more memory django is likely to use. Be careful on shared hosting + MaxRequestsPerChild 100 + + +== Example cron.py == + +from django_cron import cronScheduler, Job + +# This is a function I wrote to check a feedback email address +# and add it to our database. Replace with your own imports +from MyMailFunctions import check_feedback_mailbox + +class CheckMail(Job): + """ + Cron Job that checks the lgr users mailbox and adds any + approved senders' attachments to the db + """ + + # run every 300 seconds (5 minutes) + run_every = 300 + + def job(self): + # This will be executed every 5 minutes + check_feedback_mailbox() + +cronScheduler.register(CheckMail) diff --git a/django_cron/.svn/text-base/__init__.py.svn-base b/django_cron/.svn/text-base/__init__.py.svn-base new file mode 100644 index 0000000..b104dda --- /dev/null +++ b/django_cron/.svn/text-base/__init__.py.svn-base @@ -0,0 +1,63 @@ +""" +Copyright (c) 2007-2008, Dj Gilcrease +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +from base import Job, cronScheduler + +def autodiscover(): + """ + Auto-discover INSTALLED_APPS cron.py modules and fail silently when + not present. This forces an import on them to register any cron jobs they + may want. + """ + import imp + from django.conf import settings + + for app in settings.INSTALLED_APPS: + # For each app, we need to look for an cron.py inside that app's + # package. We can't use os.path here -- recall that modules may be + # imported different ways (think zip files) -- so we need to get + # the app's __path__ and look for cron.py on that path. + + # Step 1: find out the app's __path__ Import errors here will (and + # should) bubble up, but a missing __path__ (which is legal, but weird) + # fails silently -- apps that do weird things with __path__ might + # need to roll their own cron registration. + try: + app_path = __import__(app, {}, {}, [app.split('.')[-1]]).__path__ + except AttributeError: + continue + + # Step 2: use imp.find_module to find the app's admin.py. For some + # reason imp.find_module raises ImportError if the app can't be found + # but doesn't actually try to import the module. So skip this app if + # its admin.py doesn't exist + try: + imp.find_module('cron', app_path) + except ImportError: + continue + + # Step 3: import the app's cron file. If this has errors we want them + # to bubble up. + __import__("%s.cron" % app) + + # Step 4: once we find all the cron jobs, start the cronScheduler + cronScheduler.execute() \ No newline at end of file diff --git a/django_cron/.svn/text-base/base.py.svn-base b/django_cron/.svn/text-base/base.py.svn-base new file mode 100644 index 0000000..dbf0c3e --- /dev/null +++ b/django_cron/.svn/text-base/base.py.svn-base @@ -0,0 +1,127 @@ +""" +Copyright (c) 2007-2008, Dj Gilcrease +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +import cPickle +from threading import Timer +from datetime import datetime + +from django.dispatch import dispatcher +from django.conf import settings + +from signals import cron_done +import models + +# how often to check if jobs are ready to be run (in seconds) +# in reality if you have a multithreaded server, it may get checked +# more often that this number suggests, so keep an eye on it... +# default value: 300 seconds == 5 min +polling_frequency = getattr(settings, "CRON_POLLING_FREQUENCY", 300) + +class Job(object): + # 86400 seconds == 24 hours + run_every = 86400 + + def run(self, *args, **kwargs): + self.job() + cron_done.send(sender=self, *args, **kwargs) + + def job(self): + """ + Should be overridden (this way is cleaner, but the old way - overriding run() - will still work) + """ + pass + +class CronScheduler(object): + def register(self, job_class, *args, **kwargs): + """ + Register the given Job with the scheduler class + """ + + job_instance = job_class() + + if not isinstance(job_instance, Job): + raise TypeError("You can only register a Job not a %r" % job_class) + + job, created = models.Job.objects.get_or_create(name=str(job_instance.__class__)) + if created: + job.instance = cPickle.dumps(job_instance) + job.args = cPickle.dumps(args) + job.kwargs = cPickle.dumps(kwargs) + job.run_frequency = job_instance.run_every + job.save() + + def execute(self): + """ + Queue all Jobs for execution + """ + status, created = models.Cron.objects.get_or_create(pk=1) + + # This is important for 2 reasons: + # 1. It keeps us for running more than one instance of the + # same job at a time + # 2. It reduces the number of polling threads because they + # get killed off if they happen to check while another + # one is already executing a job (only occurs with + # multi-threaded servers) + if status.executing: + return + + status.executing = True + try: + status.save() + except: + # this will fail if you're debugging, so we want it + # to fail silently and start the timer again so we + # can pick up where we left off once debugging is done + Timer(polling_frequency, self.execute).start() + return + + jobs = models.Job.objects.all() + for job in jobs: + if job.queued: + time_delta = datetime.now() - job.last_run + if (time_delta.seconds + 86400*time_delta.days) > job.run_frequency: + inst = cPickle.loads(str(job.instance)) + args = cPickle.loads(str(job.args)) + kwargs = cPickle.loads(str(job.kwargs)) + + try: + inst.run(*args, **kwargs) + job.last_run = datetime.now() + job.save() + + except Exception: + # if the job throws an error, just remove it from + # the queue. That way we can find/fix the error and + # requeue the job manually + job.queued = False + job.save() + + status.executing = False + status.save() + + # Set up for this function to run again + Timer(polling_frequency, self.execute).start() + + +cronScheduler = CronScheduler() + diff --git a/django_cron/.svn/text-base/models.py.svn-base b/django_cron/.svn/text-base/models.py.svn-base new file mode 100644 index 0000000..cfa6640 --- /dev/null +++ b/django_cron/.svn/text-base/models.py.svn-base @@ -0,0 +1,39 @@ +""" +Copyright (c) 2007-2008, Dj Gilcrease +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +from django.db import models +from datetime import datetime + +class Job(models.Model): + name = models.CharField(max_length=100) + + # time between job runs (in seconds) // default: 1 day + run_frequency = models.PositiveIntegerField(default=86400) + last_run = models.DateTimeField(default=datetime.now()) + + instance = models.TextField() + args = models.TextField() + kwargs = models.TextField() + queued = models.BooleanField(default=True) + +class Cron(models.Model): + executing = models.BooleanField(default=False) \ No newline at end of file diff --git a/django_cron/.svn/text-base/signals.py.svn-base b/django_cron/.svn/text-base/signals.py.svn-base new file mode 100644 index 0000000..a836638 --- /dev/null +++ b/django_cron/.svn/text-base/signals.py.svn-base @@ -0,0 +1,26 @@ +""" +Copyright (c) 2007-2008, Dj Gilcrease +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +from django.dispatch import Signal + +cron_queued = Signal() +cron_done = Signal(providing_args=["job"]) \ No newline at end of file diff --git a/django_cron/README.txt b/django_cron/README.txt new file mode 100644 index 0000000..cb0320b --- /dev/null +++ b/django_cron/README.txt @@ -0,0 +1,54 @@ += How to install djang-cron = + +1. Put 'django_cron' into your python path + +2. Add 'django_cron' to INSTALLED_APPS in your settings.py file + +3. Add the following code to the beginning of your urls.py file (just after the imports): + + import django_cron + django_cron.autodiscover() + + +4. Create a file called 'cron.py' inside each installed app that you want to add a recurring job to. The app must be installed via the INSTALLED_APPS in your settings.py or the autodiscover will not find it. + +=== Important note === + +If you are using mod_python, you need to make sure your server is set up to server more than one request per instance, Otherwise it will kill django-cron before the tasks get started. The specific line to look for is in your 'httpd.conf' file: + + + # THIS IS BAD!!! IT WILL CRIPPLE DJANGO-CRON + MaxRequestsPerChild 1 + + +Change it to a value that is large enough that your cron jobs will get run at least once per instance. We're working on resolving this issue without dictating your server config. + +In the meantime, django_cron is best used to execute tasks that occur relatively often (at least once an hour). Try setting MaxRequestsPerChild to 50, 100, or 200 + + # Depending on traffic, and your server config, a number between 50 and 500 is probably good + # Note: the higher this number, the more memory django is likely to use. Be careful on shared hosting + MaxRequestsPerChild 100 + + +== Example cron.py == + +from django_cron import cronScheduler, Job + +# This is a function I wrote to check a feedback email address +# and add it to our database. Replace with your own imports +from MyMailFunctions import check_feedback_mailbox + +class CheckMail(Job): + """ + Cron Job that checks the lgr users mailbox and adds any + approved senders' attachments to the db + """ + + # run every 300 seconds (5 minutes) + run_every = 300 + + def job(self): + # This will be executed every 5 minutes + check_feedback_mailbox() + +cronScheduler.register(CheckMail) diff --git a/django_cron/__init__.py b/django_cron/__init__.py new file mode 100644 index 0000000..b104dda --- /dev/null +++ b/django_cron/__init__.py @@ -0,0 +1,63 @@ +""" +Copyright (c) 2007-2008, Dj Gilcrease +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +from base import Job, cronScheduler + +def autodiscover(): + """ + Auto-discover INSTALLED_APPS cron.py modules and fail silently when + not present. This forces an import on them to register any cron jobs they + may want. + """ + import imp + from django.conf import settings + + for app in settings.INSTALLED_APPS: + # For each app, we need to look for an cron.py inside that app's + # package. We can't use os.path here -- recall that modules may be + # imported different ways (think zip files) -- so we need to get + # the app's __path__ and look for cron.py on that path. + + # Step 1: find out the app's __path__ Import errors here will (and + # should) bubble up, but a missing __path__ (which is legal, but weird) + # fails silently -- apps that do weird things with __path__ might + # need to roll their own cron registration. + try: + app_path = __import__(app, {}, {}, [app.split('.')[-1]]).__path__ + except AttributeError: + continue + + # Step 2: use imp.find_module to find the app's admin.py. For some + # reason imp.find_module raises ImportError if the app can't be found + # but doesn't actually try to import the module. So skip this app if + # its admin.py doesn't exist + try: + imp.find_module('cron', app_path) + except ImportError: + continue + + # Step 3: import the app's cron file. If this has errors we want them + # to bubble up. + __import__("%s.cron" % app) + + # Step 4: once we find all the cron jobs, start the cronScheduler + cronScheduler.execute() \ No newline at end of file diff --git a/django_cron/base.py b/django_cron/base.py new file mode 100644 index 0000000..dbf0c3e --- /dev/null +++ b/django_cron/base.py @@ -0,0 +1,127 @@ +""" +Copyright (c) 2007-2008, Dj Gilcrease +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +import cPickle +from threading import Timer +from datetime import datetime + +from django.dispatch import dispatcher +from django.conf import settings + +from signals import cron_done +import models + +# how often to check if jobs are ready to be run (in seconds) +# in reality if you have a multithreaded server, it may get checked +# more often that this number suggests, so keep an eye on it... +# default value: 300 seconds == 5 min +polling_frequency = getattr(settings, "CRON_POLLING_FREQUENCY", 300) + +class Job(object): + # 86400 seconds == 24 hours + run_every = 86400 + + def run(self, *args, **kwargs): + self.job() + cron_done.send(sender=self, *args, **kwargs) + + def job(self): + """ + Should be overridden (this way is cleaner, but the old way - overriding run() - will still work) + """ + pass + +class CronScheduler(object): + def register(self, job_class, *args, **kwargs): + """ + Register the given Job with the scheduler class + """ + + job_instance = job_class() + + if not isinstance(job_instance, Job): + raise TypeError("You can only register a Job not a %r" % job_class) + + job, created = models.Job.objects.get_or_create(name=str(job_instance.__class__)) + if created: + job.instance = cPickle.dumps(job_instance) + job.args = cPickle.dumps(args) + job.kwargs = cPickle.dumps(kwargs) + job.run_frequency = job_instance.run_every + job.save() + + def execute(self): + """ + Queue all Jobs for execution + """ + status, created = models.Cron.objects.get_or_create(pk=1) + + # This is important for 2 reasons: + # 1. It keeps us for running more than one instance of the + # same job at a time + # 2. It reduces the number of polling threads because they + # get killed off if they happen to check while another + # one is already executing a job (only occurs with + # multi-threaded servers) + if status.executing: + return + + status.executing = True + try: + status.save() + except: + # this will fail if you're debugging, so we want it + # to fail silently and start the timer again so we + # can pick up where we left off once debugging is done + Timer(polling_frequency, self.execute).start() + return + + jobs = models.Job.objects.all() + for job in jobs: + if job.queued: + time_delta = datetime.now() - job.last_run + if (time_delta.seconds + 86400*time_delta.days) > job.run_frequency: + inst = cPickle.loads(str(job.instance)) + args = cPickle.loads(str(job.args)) + kwargs = cPickle.loads(str(job.kwargs)) + + try: + inst.run(*args, **kwargs) + job.last_run = datetime.now() + job.save() + + except Exception: + # if the job throws an error, just remove it from + # the queue. That way we can find/fix the error and + # requeue the job manually + job.queued = False + job.save() + + status.executing = False + status.save() + + # Set up for this function to run again + Timer(polling_frequency, self.execute).start() + + +cronScheduler = CronScheduler() + diff --git a/django_cron/models.py b/django_cron/models.py new file mode 100644 index 0000000..cfa6640 --- /dev/null +++ b/django_cron/models.py @@ -0,0 +1,39 @@ +""" +Copyright (c) 2007-2008, Dj Gilcrease +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +from django.db import models +from datetime import datetime + +class Job(models.Model): + name = models.CharField(max_length=100) + + # time between job runs (in seconds) // default: 1 day + run_frequency = models.PositiveIntegerField(default=86400) + last_run = models.DateTimeField(default=datetime.now()) + + instance = models.TextField() + args = models.TextField() + kwargs = models.TextField() + queued = models.BooleanField(default=True) + +class Cron(models.Model): + executing = models.BooleanField(default=False) \ No newline at end of file diff --git a/django_cron/signals.py b/django_cron/signals.py new file mode 100644 index 0000000..a836638 --- /dev/null +++ b/django_cron/signals.py @@ -0,0 +1,26 @@ +""" +Copyright (c) 2007-2008, Dj Gilcrease +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" +from django.dispatch import Signal + +cron_queued = Signal() +cron_done = Signal(providing_args=["job"]) \ No newline at end of file diff --git a/settings.py b/settings.py index 5eaa842..b0648a2 100644 --- a/settings.py +++ b/settings.py @@ -45,7 +45,7 @@ USE_I18N = True # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = '/home/nikdoof/dev/corpsso/media' +MEDIA_ROOT = '/home/dreddit/www/login.dredd.it/login/media' # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). @@ -86,6 +86,7 @@ INSTALLED_APPS = ( 'django.contrib.sessions', 'django.contrib.sites', 'registration', + 'django_cron', 'eve_proxy', 'eve_api', 'mumble',