changeset 515:ae89ba801e8b

For #194, convert the POTD management command to a celery task. Refactored to put the logic for the command into a function, and the command simply calls this function. The task can also just call this function. Added some basic tests for the new function.
author Brian Neal <bgneal@gmail.com>
date Wed, 14 Dec 2011 02:41:15 +0000
parents 6d816aa586c1
children beda97542da8
files gpp/potd/fixtures/potd_test.json gpp/potd/management/commands/pick_potd.py gpp/potd/models.py gpp/potd/tasks.py gpp/potd/tests/__init__.py gpp/potd/tests/tools_tests.py gpp/potd/tools.py gpp/settings/base.py
diffstat 8 files changed, 257 insertions(+), 65 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/potd/fixtures/potd_test.json	Wed Dec 14 02:41:15 2011 +0000
@@ -0,0 +1,73 @@
+[
+    {
+        "pk": 1, 
+        "model": "auth.user", 
+        "fields": {
+            "username": "Anonymous", 
+            "first_name": "", 
+            "last_name": "", 
+            "is_active": false, 
+            "is_superuser": false, 
+            "is_staff": false, 
+            "last_login": "1969-12-31 18:00:00", 
+            "groups": [], 
+            "user_permissions": [], 
+            "password": "!", 
+            "email": "", 
+            "date_joined": "2000-11-10 00:00:00"
+        }
+    },
+    {
+        "pk": 3, 
+        "model": "potd.photo", 
+        "fields": {
+            "description": "<p>The Deadbeats and Daikaiju after a gig at&nbsp;the&nbsp;924 Gilman club in Berkeley, California.</p>", 
+            "photo": "potd/2011/02/28/deadbeats-daikaiju.jpg", 
+            "potd_count": 6, 
+            "caption": "Daikaiju VS The Deadbeats", 
+            "user": 1, 
+            "date_added": "2011-02-28", 
+            "thumb": "potd/2011/02/28/thumbs/deadbeats-daikaiju.jpg"
+        }
+    }, 
+    {
+        "pk": 2, 
+        "model": "potd.photo", 
+        "fields": {
+            "description": "<p>The Kilaueas live on May the 29th, 2010 in Osnabr&uuml;ck, Germany. Playin` my new old 1964 sonic blue Jaguar. Tom is playing his 1972 Mustang bass. Matze is playing my Fender Custom from 1966/69. Twang cheers to everybody!</p>\r\n<p>-Ralf Kilauea.</p>", 
+            "photo": "potd/2011/02/27/kilaueas.jpg", 
+            "potd_count": 5, 
+            "caption": "The Kilaueas Live in Germany", 
+            "user": 1, 
+            "date_added": "2011-02-27", 
+            "thumb": "potd/2011/02/27/thumbs/kilaueas.jpg"
+        }
+    }, 
+    {
+        "pk": 1, 
+        "model": "potd.photo", 
+        "fields": {
+            "description": "<p>Here is a photo of Dave Wronski of Slacktone at the Tiki Caliente show in Palm Springs, CA June 6, 2010. It was 110 degrees that day.</p>", 
+            "photo": "potd/2011/02/26/wronski.jpg", 
+            "potd_count": 6, 
+            "caption": "Dave Wronski - 110 In The Shade", 
+            "user": 1, 
+            "date_added": "2011-02-26", 
+            "thumb": "potd/2011/02/26/thumbs/wronski.jpg"
+        }
+    }, 
+    {
+        "pk": 1, 
+        "model": "potd.current", 
+        "fields": {
+            "potd": 1
+        }
+    }, 
+    {
+        "pk": 1, 
+        "model": "potd.sequence", 
+        "fields": {
+            "seq": "1,2,3"
+        }
+    }
+]
--- a/gpp/potd/management/commands/pick_potd.py	Tue Dec 13 02:37:50 2011 +0000
+++ b/gpp/potd/management/commands/pick_potd.py	Wed Dec 14 02:41:15 2011 +0000
@@ -1,74 +1,15 @@
 """
-pick_potd is a custom manage.py command for the POTD application. 
-It is intended to be called from a cron job at midnight to pick the
-new POTD.
+pick_potd is a custom manage.py command for the POTD application.
+Calling it will pick a new POTD.
+
 """
-
-import random
 from django.core.management.base import NoArgsCommand
 
-from potd.models import Current
-from potd.models import Sequence
-from potd.models import Photo
+from potd.tools import pick_potd
 
-def get_sequence():
-    try:
-        s = Sequence.objects.get(pk=1)
-        if s.seq:
-            return [int(x) for x in s.seq.split(',')]
-    except:
-        pass
-    return []
-
-def new_sequence():
-    the_ids = Photo.objects.values_list('id', flat=True).order_by('id')
-    ids = []
-    for id in the_ids.iterator():
-        ids.append(int(id))
-
-    random.shuffle(ids)
-    try:
-        s = Sequence.objects.get(pk=1)
-    except Sequence.DoesNotExist:
-        s = Sequence()
-
-    s.seq = ','.join([str(id) for id in ids])
-    s.save()
-    return ids
 
 class Command(NoArgsCommand):
-    help = "Chooses the next POTD. Run this command at midnight to update the POTD."
-    #requires_model_validation = False
+    help = "Chooses the next POTD."
 
     def handle_noargs(self, **options):
-        try:
-            c = Current.objects.get(pk=1)
-            current = c.potd.pk
-        except Current.DoesNotExist:
-            c = Current()
-            current = None
-
-        seq = get_sequence()
-        if current is None or len(seq) == 0 or current == seq[-1]:
-            # time to generate a new random sequence
-            seq = new_sequence()
-            # set current to the first one in the sequence
-            if len(seq) > 0:
-                try:
-                    c.potd = Photo.objects.get(pk=seq[0])
-                    c.potd.potd_count += 1
-                    c.potd.save()
-                    c.save()
-                except:
-                    pass
-        else:
-            # find current in the sequence, pick the next one
-            try:
-                i = seq.index(current)
-                c.potd = Photo.objects.get(pk=seq[i + 1])
-                c.potd.potd_count += 1
-                c.potd.save()
-                c.save()
-            except:
-                pass
-                    
+        pick_potd()
--- a/gpp/potd/models.py	Tue Dec 13 02:37:50 2011 +0000
+++ b/gpp/potd/models.py	Wed Dec 14 02:41:15 2011 +0000
@@ -1,5 +1,6 @@
 """
 Models for the Photo Of The Day (POTD) application.
+
 """
 import os
 from PIL import ImageFile
@@ -89,7 +90,15 @@
 
 
 class CurrentManager(models.Manager):
+    """
+    Manager for the Current model.
+
+    """
     def get_current_photo(self):
+        """
+        Retrieves the current photo object from the current record.
+
+        """
         try:
             c = self.get(pk=1)
             return c.potd
@@ -97,6 +106,10 @@
             return None
 
     def get_current_id(self):
+        """
+        Returns the ID of the current POTD from the current record.
+
+        """
         potd = self.get_current_photo()
         if potd is not None:
             return potd.pk
@@ -117,7 +130,16 @@
 
 
 class SequenceManager(models.Manager):
+    """
+    Manager for the Sequence model.
+
+    """
     def insert_photo(self, photo_id):
+        """
+        Inserts the given photo_id just after the current photo so it
+        will appear as tomorrow's POTD.
+
+        """
         current = Current.objects.get_current_id()
         if current is not None:
             s = self.get(pk=1)
@@ -129,6 +151,10 @@
                 s.save()
 
     def remove_photo(self, photo_id):
+        """
+        Removes a given photo id from the sequence of photos.
+
+        """
         try:
             s = self.get(pk=1)
         except Sequence.DoesNotExist:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/potd/tasks.py	Wed Dec 14 02:41:15 2011 +0000
@@ -0,0 +1,12 @@
+"""
+Celery tasks for the POTD app.
+
+"""
+from celery.task import task
+
+import potd.tools
+
+
+@task
+def pick_potd():
+    potd.tools.pick_potd()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/potd/tests/__init__.py	Wed Dec 14 02:41:15 2011 +0000
@@ -0,0 +1,1 @@
+from tools_tests import *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/potd/tests/tools_tests.py	Wed Dec 14 02:41:15 2011 +0000
@@ -0,0 +1,35 @@
+"""
+Tests for the pick_potd() function.
+
+"""
+from django.test import TestCase
+from django.contrib.auth.models import User
+
+from potd.models import Current, Photo, Sequence
+from potd.tools import pick_potd
+
+
+class PickPotdTest(TestCase):
+
+    fixtures = ['potd_test.json']
+
+    def test_pick(self):
+
+        pick_potd()
+
+        curr = Current.objects.get(pk=1)
+        self.assertEqual(curr.potd.pk, 2)
+
+    def test_shuffle(self):
+
+        photo = Photo.objects.get(pk=3)
+        curr = Current.objects.get(pk=1)
+        curr.potd = photo
+        curr.save()
+
+        pick_potd()
+
+        ids = Sequence.objects.get(pk=1).seq.split(',')
+        curr = Current.objects.get(pk=1)
+        self.assertEqual(len(ids), 3)
+        self.assertEqual(curr.potd.pk, int(ids[0]))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gpp/potd/tools.py	Wed Dec 14 02:41:15 2011 +0000
@@ -0,0 +1,96 @@
+"""
+Tools for the Photo of the Day (potd) application.
+
+"""
+import random
+import logging
+
+from potd.models import Current, Sequence, Photo
+
+
+logger = logging.getLogger(__name__)
+
+
+def get_sequence():
+    """
+    Reads the photo sequence object from the database and converts it into a
+    list of photo IDs. If the sequence object is not found, and empty list is
+    returned.
+
+    """
+    try:
+        s = Sequence.objects.get(pk=1)
+    except Sequence.DoesNotExist:
+        return []
+
+    return [int(x) for x in s.seq.split(',')]
+
+
+def new_sequence():
+    """
+    Generates a new random sequence of photos and saves it to the database.
+    The sequence is returned as a list of photo IDs.
+
+    """
+    ids = list(Photo.objects.values_list('id', flat=True))
+    random.shuffle(ids)
+
+    try:
+        s = Sequence.objects.get(pk=1)
+    except Sequence.DoesNotExist:
+        s = Sequence()
+
+    s.seq = ','.join([str(n) for n in ids])
+    s.save()
+    return ids
+
+
+def pick_potd():
+    """
+    Chooses the next POTD. Run this command at midnight to update the POTD.
+
+    """
+    # Get the "current" record for the now old POTD:
+    try:
+        c = Current.objects.get(pk=1)
+        current = c.potd.pk
+    except Current.DoesNotExist:
+        c = Current()
+        current = None
+
+    # Get the sequence of photo ID's:
+    seq = get_sequence()
+
+    # If there is no current object, sequence, or if this was the last POTD in
+    # the sequence, generate a new random sequence:
+
+    if current is None or not seq or current == seq[-1]:
+        # time to generate a new random sequence
+        seq = new_sequence()
+        # set current to the first one in the sequence
+        if seq:
+            try:
+                c.potd = Photo.objects.get(pk=seq[0])
+            except Photo.DoesNotExist:
+                logger.error("POTD: missing photo %d", seq[0])
+            else:
+                c.potd.potd_count += 1
+                c.potd.save()
+                c.save()
+    else:
+        # find current in the sequence, pick the next one
+        try:
+            i = seq.index(current)
+        except ValueError:
+            logger.error("POTD: current photo (%d) not in sequence", current)
+            return
+
+        n = i + 1
+        try:
+            c.potd = Photo.objects.get(pk=seq[n])
+        except Photo.DoesNotExist:
+            logger.error("POTD: missing next photo %d", n)
+        else:
+            c.potd.potd_count += 1
+            c.potd.save()
+            c.save()
--- a/gpp/settings/base.py	Tue Dec 13 02:37:50 2011 +0000
+++ b/gpp/settings/base.py	Wed Dec 14 02:41:15 2011 +0000
@@ -6,6 +6,7 @@
 import django.utils.simplejson as json
 from django.contrib.messages import constants as message_constants
 import djcelery
+from celery.schedules import crontab
 
 
 PROJECT_PATH = os.path.abspath(os.path.join(os.path.split(__file__)[0], '..'))
@@ -214,6 +215,13 @@
 
 djcelery.setup_loader()
 
+CELERYBEAT_SCHEDULE = {
+    "potd": {
+        "task": "potd.tasks.pick_potd",
+        "schedule": crontab(minute=0, hour=0),
+    }
+}
+
 #######################################################################
 # GPP Specific Settings
 #######################################################################