TL; DR Это была ошибка в Django 1.7, которая была исправлена в Django 1.8.
Изменения пошли непосредственно в освоении и не гибнуть период устаревания, что не слишком удивительно, учитывая, что поддержание обратной совместимости здесь было бы очень сложно. Более удивительно то, что в 1.8 release notes не упоминалось о проблеме, поскольку исправление изменяет поведение текущего рабочего кода.
Остальная часть этого ответа является описанием того, как я нашел фиксацию с использованием git bisect run
. Это здесь для моей собственной справки больше всего на свете, поэтому я могу вернуться сюда, если мне когда-нибудь понадобится снова разделить большой проект.
Сначала мы создаем клон django и тестовый проект для воспроизведения проблемы. Я использовал virtualenvwrapper здесь, но вы можете сделать изоляцию, как хотите.
cd /tmp
git clone https://github.com/django/django.git
cd django
git checkout tags/1.7
mkvirtualenv djbisect
export PYTHONPATH=/tmp/django # get django clone into sys.path
python ./django/bin/django-admin.py startproject djbisect
export PYTHONPATH=$PYTHONPATH:/tmp/django/djbisect # test project into sys.path
export DJANGO_SETTINGS_MODULE=djbisect.mysettings
создать следующий файл:
# /tmp/django/djbisect/djbisect/models.py
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
class GFKmodel(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
gfk = GenericForeignKey()
class GRmodel(models.Model):
related_gfk = GenericRelation(GFKmodel)
также это одно:
# /tmp/django/djbisect/djbisect/mysettings.py
from djbisect.settings import *
INSTALLED_APPS += ('djbisect',)
Теперь у нас есть рабочий проект, создать test_script.py
использовать с git bisect run
:
#!/usr/bin/env python
import subprocess, os, sys
db_fname = '/tmp/django/djbisect/db.sqlite3'
if os.path.exists(db_fname):
os.unlink(db_fname)
cmd = 'python /tmp/django/djbisect/manage.py migrate --noinput'
subprocess.check_call(cmd.split())
import django
django.setup()
from django.contrib.contenttypes.models import ContentType
from djbisect.models import GFKmodel, GRmodel
ct = ContentType.objects.get_for_model(GRmodel)
y = GRmodel.objects.create(pk=456)
x = GFKmodel.objects.create(pk=789, content_type=ct, object_id=y.pk)
query1 = GRmodel.objects.values_list('related_gfk', flat=1)
query2 = GRmodel.objects.values_list('related_gfk__pk', flat=1)
print(query1)
print(query2)
print(query1.query)
print(query2.query)
if query1[0] == 789 == query2[0]:
print('FIXED')
sys.exit(1)
else:
print('UNFIXED')
sys.exit(0)
Сценарий должен быть исполняемым, поэтому добавьте флаг с chmod +x test_script.py
. Он должен быть расположен в каталоге, в который клонируется Django, т. Е. /tmp/django/test_script.py
для меня. Это связано с тем, что import django
должен сначала забрать локально проект django, а не любую версию с сайта-пакетов.
Пользовательский интерфейс мерзавца Bisect был разработан, чтобы выяснить, где ошибки появилось, поэтому обычные префиксы «плохо» и «хорошо» в обратном направлении, когда вы пытаетесь выяснить, когда определенная ошибка была неподвижными. Это может показаться несколько перевернутым, но тестовый скрипт должен выйти с успехом (код возврата 0), если ошибка присутствует, и она должна выйти из строя (с ненулевым кодом возврата), если ошибка исправлена. Это несколько раз подстегнуло меня!
git bisect start --term-new=fixed --term-old=unfixed
git bisect fixed tags/1.8
git bisect unfixed tags/1.7
git bisect run ./test_script.py
Таким образом, этот процесс будет выполнять автоматический поиск, который в конечном итоге находит фиксацию, где исправлена ошибка. Это займет некоторое время, потому что между Django 1.7 и Django 1.8 было много компромиссов. Это надвое 1362 ревизии, примерно 10 шагов, и в конечном итоге вывод:
1c5cbf5e5d5b350f4df4aca6431d46c767d3785a is the first fixed commit
commit 1c5cbf5e5d5b350f4df4aca6431d46c767d3785a
Author: Anssi Kääriäinen <[email protected]>
Date: Wed Dec 17 09:47:58 2014 +0200
Fixed #24002 -- GenericRelation filtering targets related model's pk
Previously Publisher.objects.filter(book=val) would target
book.object_id if book is a GenericRelation. This is inconsistent to
filtering over reverse foreign key relations, where the target is the
related model's primary key.
Это точно коммита, где запрос был изменен с некорректной SQL (который получает данные от неправильной таблицы)
SELECT "djbisect_gfkmodel"."object_id" FROM "djbisect_grmodel" LEFT OUTER JOIN "djbisect_gfkmodel" ON ("djbisect_grmodel"."id" = "djbisect_gfkmodel"."object_id" AND ("djbisect_gfkmodel"."content_type_id" = 8))
в правильная версия:
SELECT "djbisect_gfkmodel"."id" FROM "djbisect_grmodel" LEFT OUTER JOIN "djbisect_gfkmodel" ON ("djbisect_grmodel"."id" = "djbisect_gfkmodel"."object_id" AND ("djbisect_gfkmodel"."content_type_id" = 8))
конечно, от совершения хэш мы можем найти запрос тянущий и билет легко на GitHub. Надеюсь, это может помочь кому-то еще в один день - дезактивация Django может быть сложной настройкой из-за миграции!
* примечание: * версия Django '(1, 7, 11, 'final', 0)'. Я не могу воспроизвести это в Django 1.8. – wim
Может ли это означать, что в Django 1.7 они решили исправить для 1.8? – mgilson
Возможно, но я искал высоко и низко для упоминания в примечаниях к выпуску и не мог найти его. Я полагаю, что 'git bisect' мог найти его ... – wim