17

I have a table with an IntegerField (hit_count), and when a page is visited (for example, http://site/page/3) I want record ID 3's hit_count column in the database to increment by 1.

The query should be like:

update table set hit_count = hit_count + 1 where id = 3

Can I do this with the standard Django Model conventions? Or should I just write the query by hand?

Cairnarvon
  • 25,981
  • 9
  • 51
  • 65

4 Answers4

33

If you use Django 1.1+, just use F expressions:

from django.db.models import F
...
MyModel.objects.filter(id=...).update(hit_count=F('hit_count')+1)

This will perform a single atomic database query.

As gerdemb says, you should consider putting this in a middleware to make it easily reusable so it doesn't clutter up all your views.


yprez
  • 14,854
  • 11
  • 55
  • 70
Carl Meyer
  • 122,012
  • 20
  • 106
  • 116
  • 1
    +1: http://docs.djangoproject.com/en/dev/topics/db/queries/#filters-can-reference-fields-on-the-model – S.Lott Mar 08 '09 at 18:49
  • Thanks - meant to link to that but forgot. I'll add it. – Carl Meyer Mar 08 '09 at 21:06
  • 2
    What I think you want is `MyModel.objects.get(id=...)` as opposed to `filter`; if we know it's a single row, why not make it clear? – Jared Forsyth May 01 '10 at 23:41
  • 7
    @Jared Because .get() is not lazy (it actually queries the database). Which is why individual model objects don't have a .update() method; once you've already queried, you just use .save(). But then you lose atomicity, which is the whole point here. The .update() method for atomic updates is only available on QuerySets, and is only possible because they are lazy. – Carl Meyer May 04 '10 at 12:55
  • Oh, that's interesting. Thanks. – Jared Forsyth May 04 '10 at 15:04
  • How would it be possible to get the right model ID in the middleware, depending on the template that is being requested, so that the right model hit count would be updated? – sequence Jul 06 '22 at 23:23
4

As gerdemb says, you should write it into a middleware to make it really reusable. Or (simpler) write a function decorator. In fact, there are adaptors to use a middleware as a decorator and viceversa.

But if you're worried about performance and want to keep the DB queries per page hit low, you can use memcached's atomic increment operation. of course, in this case, you have to take care of persistence yourself.

Javier
  • 60,510
  • 8
  • 78
  • 126
0

The model conventions won't be atomic; write it by hand:

BEGIN
SELECT * FROM table WHERE id=3 FOR UPDATE
UPDATE table SET hit_count=hit_count+1 WHERE id=3
COMMIT
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
0

I would start by looking at the documentation for middleware. Ignacio's comment about the lack of atomic transactions is true, but this is a problem with the entire Django framework. Unless you're concerned about having a 100% accurate counter I wouldn't worry about it.

gerdemb
  • 11,275
  • 17
  • 65
  • 73
  • How is this a "problem with the entire Django framework?" You can have atomic transaction per request with TransactionMiddleware, or get finer-grained control with the functions in django.db.transaction. – Carl Meyer Mar 08 '09 at 16:42
  • Sure you can have transactions, but they are not enabled by default. If you build a simple CRUD application using the built-in Django admin you're going to have race conditions. Even the tutorial in the Django documentation has these kinds of problems. – gerdemb Mar 09 '09 at 22:39
  • The way you've phrased it makes it sound like an unavoidable problem, which is of course misleading to someone that may not know as much about Django as you. – Jordan Jun 21 '10 at 03:06