On Wed, 2009-01-21 at 19:38 -0800, Keyton Weissinger wrote: > OK. I figured it out and it was VERY STRANGE. Maybe some one reading > this can tell me WWWHHHYYY my solution works. For now, it's voodoo to > me...
You're going to kick yourself after this. The reason for the failure turns out to be really easy to understand, now that your show the code. :-) > > Turns out that some of my code (in the view where I was having > trouble) called some code that looked sort of like this: Hmm .. you didn't exactly simplify your failing case to the smallest possible example, did you? All this extra stuff would have set off a lot of alarm bells about other places to look (although my first thought would have been whether you could do without it and retest to see if that changed anything). Before getting into the more-or-less two line explanation, some background information. Firstly, model._meta is obviously internal API, so there are some shortcuts taken for performance and maintainability reasons. We don't send the black helicopters after people who access it, but it's very much "user beware" country. It's one of the areas that is currently intentionally undocumented, for example (we'll eventually drop some documentation about internal datastructures into docs/internals/, but have had other things to do so far). Of relevance to this particular case, we use lists a lot internally and return references to them from certain methods. We use lists, rather than tuples, because they are updated a fair bit during creation and all the extra copying isn't free (updating a tuple requires a copy). Lists are just nicer to work with, generally. > field_list = model._meta.fields Have a look at the implementation here (django.db.models.options.Options._fields(), since _meta.fields is a property). It returns a reference to a list. A *reference*, not a copy! Your code would have worked if you wrote # Work with a copy of the fields list. field_list = model._meta.fields[:] > field_list.extend(model._meta.many_to_many) Here you update that reference in place. So you've just corrupted one of the internal data structures of self._meta. That's pretty much going to guarantee that the wheels will fall off at some point in the future. If not in the way you saw, in some other really hard to debug fashion. It's kind of a Python trap and if you're designing a public API, you would generally return a copy of the list if the caller wasn't expected to change the internal structure. In the Options class, we "know" (in quotes, because there are people who call it from public code, but that's their problem, not ours) that the only callers are functions that are read-only on the data structure, so we save a bit of time and code complexity. We could return a copy each time, but we access self._fields a *lot*, particularly in queryset operations. Things might have changed a bit in the last six months (although not dramatically), but when I was working on the queryset-refactor branch, I spent a lot of time profiling various common execution paths and a lot of the newer shared data structures in Options are implemented based on that work (self.fields used to be a simple list, for example, but when model inheritance came in, it became, internally, more complicated). Regards, Malcolm --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Django users" group. To post to this group, send email to django-users@googlegroups.com To unsubscribe from this group, send email to django-users+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/django-users?hl=en -~----------~----~----~----~------~----~------~--~---