views:

147

answers:

2

I am new to Django and I am trying to build a blog myself. I'm trying to create a feature I've seen implemented in Drupal using the nodequeue module.

What I want to do is to be able to create queues of objects, for example, queues of blog posts. Below, I describe how I imagine the queues to work:

  • the size of each queue should be user-defined.
  • the date an object is added to a queue should be recorded.
  • I would like to be able to define the order of the items that belong to each queue (but I think this would be very difficult).
  • if the queue is full, the addition of an extra item should discard the oldest item of the queue.

An example of how such a feature would be useful is the creation of a featured posts queue.

My current knowledge does not allow me to figure out what would be the right way to do it. I would appreciate any pointers.

Thanks in advance

+4  A: 

Here's one approach:

import collections, datetime, itertools

class nodequeue(object):
  def __init__(self, N):
    self.data = collections.deque(N * [(None, None)])
  def add(self, anobj):
    self.data.popleft()
    self.data.push((anobj, datetime.datetime.now())
  def __iter__(self):
    it = iter(self.data)
    return it.dropwhile(lambda x: x[1] is None, self.data)

This ignores the "ordering" desiderata, but that wouldn't be too hard to add, e.g.:

class nodequeueprio(object):
  def __init__(self, N):
    self.data = collections.deque(N * [(None, None, None)])
  def add(self, anobj, prio):
    self.data.popleft()
    self.data.push((anobj, datetime.datetime.now(), prio)
  def __iter__(self):
    it = iter(self.data)
    return sorted(it.dropwhile(lambda x: x[1] is None, self.data),
                  key=operator.itemgetter(2))

I think that prefilling the queue with placeholder Nones simplifies the code because add can always drop the leftmost (oldest or None) item before adding the new thingy -- even though __iter__ must then remove the placeholders, that's not too bad.

Alex Martelli
Hi Alex. Thanks for this sample code. I confess that the 'return' statements are a bit advanced for my level in python programming. I will have to study the docs for a while :) The 'collections' module and the functionality you demonstrate above give me some ideas on how to implement the same functionality using the Django models and modelforms.
Born To Ride
@Born, I would do it in pure Python (it can be done with lower-level Python, I just find itertools simpler than pedestrian loops!-) so you can reuse it from Django's models, modelforms, AND other places yet (custom template tags, whatever).
Alex Martelli
+3  A: 

Alex's approach is great. I won't pretend to compete with his level of expertise, but for sake of completeness, here is another approach using the fantastic Queue.Queue class (bonus: thread-safe, but that's kinda useless to you based on your description). This might be easier to understand for you, as you expressed some concern on that point:

myqueue.py

#!/usr/bin/python

# Renamed in Python 3.0
try: from Queue import Queue, Full, Empty
except: from queue import Queue, Full, Empty
from datetime import datetime

# Spec 1: Size of each queue should be user-defined.
#   - maxsize on __init__

# Spec 2: Date an object is added should be recorded.
#   - datetime.now() is first member of tuple, data is second

# Spec 3: I would like to be able to define the order of the items that
# belong to each queue.
#   - Order cannot be rearranged with this queue.

# Spec 4: If the queue is full, the addition of an extra item should discard
# the oldest item of the queue.
#   - implemented in put()

class MyQueue(Queue):
    "Wrapper around Queue that discards old items instead of blocking."
    def __init__(self, maxsize=10):
        assert type(maxsize) is int, "maxsize should be an integer"
        Queue.__init__(self, maxsize)

    def put(self, item):
        "Put an item into the queue, possibly discarding an old item."
        try:
            Queue.put(self, (datetime.now(), item), False)
        except Full:
            # If we're full, pop an item off and add on the end.
            Queue.get(self, False)
            Queue.put(self, (datetime.now(), item), False)

    def put_nowait(self, item):
        "Put an item into the queue, possibly discarding an old item."
        self.put(item)

    def get(self):
        "Get a tuple containing an item and the datetime it was entered."
        try:
            return Queue.get(self, False)
        except Empty:
            return None

    def get_nowait(self):
        "Get a tuple containing an item and the datetime it was entered."
        return self.get()


def main():
    "Simple test method, showing at least spec #4 working."
    queue = MyQueue(5)
    for i in range(1, 7):
        queue.put("Test item number %u" % i)

    while not queue.empty():
        time_and_data = queue.get()
        print "%s => %s" % time_and_data


if __name__ == "__main__":
    main()

expected output

2009-11-02 23:18:37.518586 => Test item number 2
2009-11-02 23:18:37.518600 => Test item number 3
2009-11-02 23:18:37.518612 => Test item number 4
2009-11-02 23:18:37.518625 => Test item number 5
2009-11-02 23:18:37.518655 => Test item number 6
Jed Smith
Hi Jed. This is an excellent example of how to modify the default Queue class. Thanks for writing it :) Some things still puzzle me though about how to implement a queue with the above functionality using Django's models and the admin app, for example what django element should represent the queue? Should it be a record in a table? Should it be an inline in ModelAdmin (with max_num equal to the size of the queue)?
Born To Ride
@Born: I'm teaching myself Django next weekend (never gotten around to it), so I'm not sure. (Halp, other commenters!)
Jed Smith
@Jed: I guess I've chosen a difficult task to accomplish as a n00b! :D
Born To Ride