views:

28

answers:

2

I have model, Match, with two foreign keys:

class Match(model.Model):
   winner = models.ForeignKey(Player)
   loser = models.ForeignKey(Player)

When I loop over Match I find that each model instance uses a unique object for the foreign key. This ends up biting me because it introduces inconsistency, here is an example:

>>> def print_elo(match_list):
...     for match in match_list:
...         print match.winner.id, match.winner.elo
...         print match.loser.id, match.loser.elo
... 
>>> print_elo(teacher_match_list)
4 1192.0000000000
2 1192.0000000000
5 1208.0000000000
2 1192.0000000000
5 1208.0000000000
4 1192.0000000000
>>> teacher_match_list[0].winner.elo = 3000
>>> print_elo(teacher_match_list) 
4 3000            # Object 4
2 1192.0000000000
5 1208.0000000000
2 1192.0000000000
5 1208.0000000000
4 1192.0000000000 # Object 4
>>>

I solved this problem like so:

def unify_refrences(match_list):
    """Makes each unique refrence to a model instance non-unique.

    In cases where multiple model instances are being used django creates a new
    object for each model instance, even if it that means creating the same
    instance twice. If one of these objects has its state changed any other
    object refrencing the same model instance will not be updated. This method
    ensure that state changes are seen. It makes sure that variables which hold
    objects pointing to the same model all hold the same object.

    Visually this means that a list of [var1, var2] whose internals look like so:

        var1 --> object1 --> model1
        var2 --> object2 --> model1

    Will result in the internals being changed so that:

        var1 --> object1 --> model1
        var2 ------^
    """
    match_dict = {}
    for match in match_list:
        try:
            match.winner = match_dict[match.winner.id]
        except KeyError:
            match_dict[match.winner.id] = match.winner
        try:
            match.loser = match_dict[match.loser.id]
        except KeyError:
            match_dict[match.loser.id] = match.loser

My question: Is there a way to solve the problem more elegantly through the use of QuerySets without needing to call save at any point? If not, I'd like to make the solution more generic: how can you get a list of the foreign keys on a model instance or do you have a better generic solution to my problem?

Please correct me if you think I don't understand why this is happening.

A: 

Uh, are you using get_or_create() for the Player records? If not, then you are probably creating new instances of identical (or near identical) Player records on every match. This can lead to tears and/or insanity.

Peter Rowell
I don't think that the creation of the Player objects is a factor. I'm using something like combinations(player_list, 2) from the itertools module to help create match objects. I just tested this module and it didn't do a deep copy.
Joshua
A: 

This is because, as far as I can tell, there's no global cache of model instances, so each query creates new instances, and your lists of related objcts are created lazily using separate queries.

You might find that select_related() is smart enough to solve the problem in this case. Instead of code like:

match = Match.objects.filter(...).get()

use:

match = Match.objects.select_related().filter(...).get()

That creates all the attribute instances at once and may be smart enough to re-use instances. Otherwise, you are going to need some kind of explicit cache (which is what your solution does).

Warning: I am surprised by this kind of behaviour myself and am not an expert on this. I found this post while searching for information on this kind of issue in my own code. I'm just sharing what I think is happening as I try to understand...

andrew cooke