views:

49

answers:

4

Hay, i have a simple model

class Manufacturer(models.Model):
    name = models.CharField()
    car_count = models.IntegerField()

class Car(models.Model):
    maker = ForeignKey(Manufacturer)

I want to update the car_count field when a car is added to a manufacturer, I'm aware i could just count the Manufacturer.car_set() to get the value, but i want the value to be stored within that car_count field.

How would i do this?

EDIT

Would something like this work?

    def save(self):
        if self.id:
            car_count = self.car_set.count()
            self.save()
+4  A: 

The best way make something happen when a model is saved it to use a signal. Django's documentation does a good job of describing what signals are and how to use them: http://docs.djangoproject.com/en/dev/topics/signals/

I'm not sure why you need to make it a field in the model though. Databases are very good at counting rows, so you could add a model method to count the cars which would use a very fast COUNT() query.

class Manufacturer(models.Model):
    name = models.CharField()

    def car_count(self):
        return Car.objects.filter(maker=self).count()

class Car(models.Model):
    maker = ForeignKey(Manufacturer)

In light of the requirement added by your comment, you're back to updating a field on the Manufacturer model whenever a Car is saved. I would still recommend using the count() method to ensure the car_count field is accurate. So your signal handler could look something like this:

def update_car_count(sender, **kwargs):
    instance = kwargs['instance']
    manufacturer = instance.maker
    manufacturer.car_count = Car.objects.filter(maker=self).count()
    manufacturer.save()

Then you would connect it to both the post_save and post_delete signals of the Car model.

post_save.connect(update_car_count, sender=Car)
post_delete.connect(update_car_count, sender=Car)
Josh Wright
i want to be able to order manufactures by car_count. As far as i can see i cannot do .order_by('car_count()') in a query?
dotty
Ah, no, you can't use a model method to order on, so you would need to make it a field.
Josh Wright
Any idea how i would use 'signals' to update this 'car_count' field when the Manufacturer has another Car added to it? I'm asusming I'll need to use post.save() ?
dotty
+2  A: 

I'm a tiny bit confused.

.. when a car is added to a manufacturer ..

In the code shown in your question, I'd guess, you save a car with some manufacturer, e.g.

car.maker = Manufacturer.objects.get(name='BMW')
car.save()

Then the save method of the Car class would need to take update the car_count of the manufacturer (see Overriding predefined model methods for more details).

def save(self, *args, **kwargs):
    if self.id:
        self.maker.car_count = len(self.maker.car_set.all())
    super(Car, self).save(*args, **kwargs)

Since this isn't the most elegant code, I'd suggest as @Josh Wright to look into signals for that matter.

P.S. You could also add a method on the Manufacturer class, but I guess, you want this attribute to live in the database.

class Manufacturer(models.Model):
    name = models.CharField()

    def _car_count(self):
        return len(self.car_set.all())

    car_count = property(_car_count)

...
The MYYN
I would create a Car object, then use Manufacturer.car_set.add(Car) to add the Car to the Manufacturer, when the Car has been added i want to update the car_count with the amount of Car objects the Manufacturer has. This clear things up any more?
dotty
OK, this wasn't clear for me from your question; then you code should work (and I guess in an OK way), just use the correct method signature, that is `def save(self, *args, **kwargs)` and call super somewhere in between: `super(Manufacturer, self).save(*args, **kwargs)`
The MYYN
You reply almost worked, i can call car_count in the shell and it displays the correct value, however if i try using that value in my view.py to order by it, its fails with the old Cannot resolve keyword 'car_count' into field. error.
dotty
to tell from remote, difficult it is -
The MYYN
I found a solution, i just manually updated the car_count field when i add a new Car to the manufacturer. I wanted it to be a little more cleaner, as in all the work done in the model. But i guess I'll have to use the view. Works though.
dotty
Remember to update car_count when you delete a car as well.
Josh Wright
The override above won't work, since `Car.id` won't be set until it's saved. I'd do something like: `def save(self, *args, **kwargs):. super(Car, self).save(*args, **kwargs). self.maker.car_count = len(self.maker.car_set.all()). self.maker.save()` Which is untested, and the formatting's screwy, but should work.
Anthony Briggs
A: 

The override in MYYN's answer won't work, since Car.id won't be set (and probably not included in the Manufacturer's car_set) until it's saved. Instead, I'd do something like:

def save(self, *args, **kwargs):
    super(Car, self).save(*args, **kwargs)
    self.maker.car_count = len(self.maker.car_set.all())
    self.maker.save()

Which is untested, but should work.

Of course, the best way is to use Josh's solution, since that's going 'with the grain' of Django.

Anthony Briggs
+1  A: 

The proper way to let the database show how many cars a manufacturer has, is to let the database calculate it in the view using aggregations.

from django.db.models import Count
Manufacturer.objects.all().annotate(car_count=Count(car)).order_by('car_count')

Databases are very efficient at that sort of thing, and you can order by the result as seen above.

Daniel Roseman