[CentralOH] Counting References in Django

Eric Floehr eric at intellovations.com
Tue Dec 11 22:18:35 CET 2012


Jim,

On Thu, Dec 6, 2012 at 7:43 PM, <jep200404 at columbus.rr.com> wrote:

> In Django, what's a better way of counting references to a record
> of an arbitrary model than my following working code?
>
>     c = 0
>     for r in record._meta.get_all_related_objects():
>         c += r.model.objects.filter(
>             **{r.field.name + '__exact': record.id}).count()
>
> I can rearrange that into a single statement as follows,
> but it's harder to read, so that's not better.
>
>     c = sum(map(
>         (lambda r: r.model.objects.filter(
>         **{r.field.name + '__exact': record.id}).count()),
>         record._meta.get_all_related_objects()))
>


The code as it is, is good. It's a clever way to use introspection in
Django to count references to a model instance.  _meta is a nice interface
(even if it is internal) to easily find out about the model and do what you
did in a general way that is applicable to any model.  You don't have to
write specific code for each model, and worry about having to change it
every time you add a new model that points to the model you'd like to keep
track of how many are referencing it.

But you asked about a better way, and here are my thoughts on that.  In
programming, reference counting is very important. One huge use case is in
garbage collected languages like Java, Python, or Go. The runtime needs an
easy, efficient way to know when an object is no longer being referred to
and thus it's memory can be deallocated.  We use counts because that is the
best way to accomplish that. Rarely do we care *what* is pointing to us,
just that there is something.

However, a relational database gives us all kinds of facilities for knowing
what is pointing to a row (i.e. an object), it's what it's about... they
don't call them *relational* for nothing :-). So when you are pondering
counting references in a database context, it's always a good idea to step
back and ask yourself, "What does a count give me that the existing
facilities in my relational database don't already".

A count makes sense in answering questions like "How many books point to
this author?", or "How many observations point to this location?". But your
code is very general and cannot answer specific questions like that. Your
code could answer the question "Is there anything pointing to this
record?", but that's about it. So it might be good to take a step back and
ask why you are asking that question, and if there are better ways of
asking.

You might be asking that question to see if it was ok to delete a record.
 If you are asking if anything is pointing to it, you must only want to
delete a record if nothing is pointing at it.  Django's Model.delete()
method will not only delete the record the instance of the Model is
representing, but it will also look at all the foreign key relationships
and delete any records that are currently pointing to that record. By
default, Django does a "cascaded delete".

So if you delete an author, any books that point to that author will also
be deleted. So you use your code above to check to see if anything is
pointing to that author, and if the answer is 0, go ahead and delete.

But you don't have to do that. There is a parameter to ForeignKey that
allows you to specify how you want to handle deletes. The default is
CASCADE (and it's cascade even though it will construct a PostgreSQL
database without cascade, so if you do the same delete in raw SQL, it'll
not work). But you can choose PROTECT, which will then do the same thing
PostgreSQL will do by default, which is not allow you to delete an object
that is still being referenced.  You can also choose SET_NULL or
SET_DEFAULT, which on a delete will set the referencing object's reference
to either NULL or the default value specified. You can also pass in a
call-back to call custom code to do whatever.

Because a relational database handles the relations for us by default, we
rarely have to use reference counting in a general sense, however, because
Django's ORM supports a number of different database platforms, with
varying degrees of referential integrity, it is handing in software what
PostgreSQL handles by default. And by default, what Django creates in the
PostgreSQL database it makes doesn't match what it does in code.  I.e.
"DELETE FROM authors WHERE id=1;" will not give the same results as
"Authors.objects.get(pk=1).delete()" if that author is being pointed to by
one or more books.  And that's really confusing.

-Eric
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/centraloh/attachments/20121211/0e4531b5/attachment.html>


More information about the CentralOH mailing list