Ответ 1
TL; DR Это была ошибка в Django 1.7, которая была исправлена в Django 1.8.
- Исправить фиксацию: 1c5cbf5e5d5b350f4df4aca6431d46c767d3785a
- Исправить PR: GenericRelation фильтрация целей, связанных с моделью pk
- Биг-билет: Должен фильтровать значение первичного ключа связанной модели, а не значение object_id
Изменения пошли непосредственно на мастер и не прошли период устаревания, что не слишком удивительно, учитывая, что поддержание обратной совместимости здесь было бы очень сложно. Более удивительно то, что в примечаниях 1.8 не упоминалось о проблеме, поскольку исправление изменяет поведение текущего рабочего кода.
Остальная часть этого ответа - это описание того, как я нашел фиксацию с помощью 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)
script должен быть исполняемым, поэтому добавьте флаг chmod +x test_script.py
. Он должен быть расположен в каталоге, в который Django клонирован, т.е. /tmp/django/test_script.py
для меня. Это связано с тем, что import django
сначала берет локальный проект django, а не любую версию из пакетов сайта.
Пользовательский интерфейс git bisect был разработан, чтобы выяснить, где появились ошибки, поэтому обычные префиксы "плохого" и "хорошего" обращаются назад, когда вы пытаетесь выяснить, когда исправлена определенная ошибка. Это может показаться несколько перевернутым, но тест script должен выйти с успехом (код возврата 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 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 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) )
Конечно, из хэша commit мы можем легко найти запрос на вытягивание и билет на github. Надеюсь, это может помочь кому-то еще один день - дезактивация Django может быть сложной настройкой из-за миграции!