Reputation: 547
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.
Upvotes: 3
Views: 747
Reputation: 16367
I've found a good answer to a similar question, take a look here:
Django Models: preserve object identity over foreign-key following
Upvotes: 0
Reputation: 396
You might want to check out django-idmapper It defines a SharedMemoryModel so that there is only one copy of each instance in the interpreter.
Upvotes: 1
Reputation: 46872
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...
Upvotes: 2
Reputation: 17713
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.
Upvotes: 0