views:

109

answers:

4

I'm working on a website where I sell products (one class Sale, one class Product). Whenever I sell a product, I want to save that action in a History table and I have decided to use the observer pattern to do this.

That is: my class Sales is the subject and the History class is the observer, whenever I call the save_sale() method of the Sales class I will notify the observers. (I've decided to use this pattern because later I'll also send an email, notify the admin, etc.)

This is my subject class (the Sales class extends from this)

class Subject:
    _observers = []

    def attach(self, observer):
        if not observer in self._observers:
            self._observers.append(observer)

    def detach(self, observer):
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    def notify(self,**kargs):
        for observer in self._observers:
            observer.update(self,**kargs)

on the view I do something like this

sale = Sale()
sale.user = request.user
sale.product = product
h = History() #here I create the observer
sale.attach(h) #here I add the observer to the subject class
sale.save_sale() #inside this class I will call the notify() method

This is the update method on History

def update(self,subject,**kargs):
    self.action = "sale"
    self.username = subject.user.username
    self.total = subject.product.total
    self.save(force_insert=True)

It works fine the first time, but when I try to make another sale, I get an error saying I can't insert into History because of a primary key constraint.

My guess is that when I call the view the second time, the first observer is still in the Subject class, and now I have two history observers listening to the Sales, but I'm not sure if that's the problem (gosh I miss the print_r from php).

What am I doing wrong? When do I have to "attach" the observer? Or is there a better way of doing this?

BTW: I'm using Django 1.1 and I don't have access to install any plugins.

+8  A: 

This may not be an acceptable answer since it's more architecture related, but have you considered using signals to notify the system of the change? It seems that you are trying to do exactly what signals were designed to do. Django signals have the same end-result functionality as Observer patterns.

http://docs.djangoproject.com/en/1.1/topics/signals/

Andrew Sledge
i read that page and i'll give it a try if i cant find any other solution.. I didn't want to depend on something developed specially for django, cause tomorrow i'll need to do this in PHP and i wanted to have the know-how
pleasedontbelong
+1  A: 

@Andrew Sledge's answer indicates a good way of tackling this problem. I would like to suggest an alternate approach.

I had a similar problem and started out using signals. They worked well but I found that my unit tests had become slower as the signals were called each time I loaded an instance of the associated class using a fixture. This added tens of seconds to the test run. There is a work around but I found it clumsy. I defined a custom test runner and disconnected my functions from the signals before loading fixtures. I reconnected them afterwards.

Finally I decided to ditch signals altogether and overrode the appropriate save() methods of models instead. In my case whenever an Order is changed a row is automatically created in and OrderHistory table, among other things. In order to do this I added a function to create an instance of OrderHistory and called it from within the Order.save() method. this also made it possible to test the save() and the function separately.

Take a look at this SO question. It has a discussion about when to override save() versus when to use signals.

Manoj Govindan
in the end using signals seems too "django" for me, i'd rather use something that could be explained in terms of OOP. So one day i could use the same logic in another language.. what i thaught in the end is to override the save() of my Sale class where i'd call the notify() method
pleasedontbelong
IMHO signals (which are but callbacks) can be handled in OO way or otherwise. Python supports both OO and non-OO styles and consequently so does Django. That said it is up to you to take a call.
Manoj Govindan
+1  A: 

Thank you all for your answers, reading about signals gave me another perspective but i dont want to use them because of learning purposes (i wanted to use the observer pattern in web development :P) In the end, i solved doing something like this:

class Sales(models.Model,Subject):
    ...
    def __init__(self):
        self._observers = []  #reset observers
        self.attach(History()) #attach a History Observer
    ...
    def save(self):
        super(Sales,self).save()
        self.notify() # notify all observers

now every time i call the save(), the observers will be notified and if i need it, i could add or delete an observer

what do you think? is this a good way to solve it?

pleasedontbelong
Yes, but you can self._observers = [] in the Subject constructor.
dmitko
+2  A: 

I think this is because _observers = [] acts like static shared field. So every instance of Subject changes the _observers instance and it has unwanted side effect.

Initialize this variable in constructor:

class Subject:

    def __init__(self):
        self._observers = []
dmitko
i've tried that the other day. But it seems that the constructor of Subject is never called, i even tried to call it from the constructor of the child class using super(Sales,self).__init__() but i had no luck
pleasedontbelong
If a base class has an __init__() method, the derived class’s __init__() method, if any, must explicitly call it to ensure proper initialization of the base class part of the instance; for example: BaseClass.__init__(self, [args...])
dmitko
http://docs.python.org/reference/datamodel.html#object.__init__
dmitko
yeap.. i called it explicitly.. but it didn't work, maybe it's because i have multiple inheritance for Sales
pleasedontbelong
hm... may be. if you figure out why it is so - please, let me know.
dmitko
http://stackoverflow.com/questions/3699713/multiple-inheritance-in-django-problem-with-constructors/3699814#3699814
pleasedontbelong
hey thxs for pointing where my problem was.. I had some problems with the constructors in multiple inheritance, but now that's solved and my observer pattern works fine!
pleasedontbelong
thank you as well for the link!
dmitko