Fixed #3215, #3081, #2749 -- Fixed problem with mistaken deletion of objects when a GenericRelation is involved. Thanks to Thomas Steinacher for helping to narrow down the problem (#3215), and Alex Dedul (#3081) for the starting point of a working patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@4428 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
dfee6b328e
commit
12ad69c0b4
|
@ -1,5 +1,6 @@
|
||||||
from django.db import backend, connection, transaction
|
from django.db import backend, connection, transaction
|
||||||
from django.db.models.fields import DateField, FieldDoesNotExist
|
from django.db.models.fields import DateField, FieldDoesNotExist
|
||||||
|
from django.db.models.fields.generic import GenericRelation
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.dispatch import dispatcher
|
from django.dispatch import dispatcher
|
||||||
from django.utils.datastructures import SortedDict
|
from django.utils.datastructures import SortedDict
|
||||||
|
@ -979,18 +980,26 @@ def delete_objects(seen_objs):
|
||||||
|
|
||||||
pk_list = [pk for pk,instance in seen_objs[cls]]
|
pk_list = [pk for pk,instance in seen_objs[cls]]
|
||||||
for related in cls._meta.get_all_related_many_to_many_objects():
|
for related in cls._meta.get_all_related_many_to_many_objects():
|
||||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
if not isinstance(related.field, GenericRelation):
|
||||||
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
|
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||||
(qn(related.field.m2m_db_table()),
|
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
|
||||||
qn(related.field.m2m_reverse_name()),
|
(qn(related.field.m2m_db_table()),
|
||||||
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
|
qn(related.field.m2m_reverse_name()),
|
||||||
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
|
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
|
||||||
|
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
|
||||||
for f in cls._meta.many_to_many:
|
for f in cls._meta.many_to_many:
|
||||||
|
if isinstance(f, GenericRelation):
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
query_extra = 'AND %s=%%s' % f.rel.to._meta.get_field(f.content_type_field_name).column
|
||||||
|
args_extra = [ContentType.objects.get_for_model(cls).id]
|
||||||
|
else:
|
||||||
|
query_extra = ''
|
||||||
|
args_extra = []
|
||||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||||
cursor.execute("DELETE FROM %s WHERE %s IN (%s)" % \
|
cursor.execute(("DELETE FROM %s WHERE %s IN (%s)" % \
|
||||||
(qn(f.m2m_db_table()), qn(f.m2m_column_name()),
|
(qn(f.m2m_db_table()), qn(f.m2m_column_name()),
|
||||||
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]])),
|
','.join(['%s' for pk in pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE]]))) + query_extra,
|
||||||
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE])
|
pk_list[offset:offset+GET_ITERATOR_CHUNK_SIZE] + args_extra)
|
||||||
for field in cls._meta.fields:
|
for field in cls._meta.fields:
|
||||||
if field.rel and field.null and field.rel.to in seen_objs:
|
if field.rel and field.null and field.rel.to in seen_objs:
|
||||||
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
|
||||||
|
|
|
@ -65,14 +65,14 @@ __test__ = {'API_TESTS':"""
|
||||||
|
|
||||||
# Objects with declared GenericRelations can be tagged directly -- the API
|
# Objects with declared GenericRelations can be tagged directly -- the API
|
||||||
# mimics the many-to-many API.
|
# mimics the many-to-many API.
|
||||||
>>> lion.tags.create(tag="yellow")
|
|
||||||
<TaggedItem: yellow>
|
|
||||||
>>> lion.tags.create(tag="hairy")
|
|
||||||
<TaggedItem: hairy>
|
|
||||||
>>> bacon.tags.create(tag="fatty")
|
>>> bacon.tags.create(tag="fatty")
|
||||||
<TaggedItem: fatty>
|
<TaggedItem: fatty>
|
||||||
>>> bacon.tags.create(tag="salty")
|
>>> bacon.tags.create(tag="salty")
|
||||||
<TaggedItem: salty>
|
<TaggedItem: salty>
|
||||||
|
>>> lion.tags.create(tag="yellow")
|
||||||
|
<TaggedItem: yellow>
|
||||||
|
>>> lion.tags.create(tag="hairy")
|
||||||
|
<TaggedItem: hairy>
|
||||||
|
|
||||||
>>> lion.tags.all()
|
>>> lion.tags.all()
|
||||||
[<TaggedItem: hairy>, <TaggedItem: yellow>]
|
[<TaggedItem: hairy>, <TaggedItem: yellow>]
|
||||||
|
@ -105,4 +105,30 @@ __test__ = {'API_TESTS':"""
|
||||||
[<TaggedItem: shiny>]
|
[<TaggedItem: shiny>]
|
||||||
>>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id)
|
>>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id)
|
||||||
[<TaggedItem: clearish>]
|
[<TaggedItem: clearish>]
|
||||||
|
|
||||||
|
# If you delete an object with an explicit Generic relation, the related
|
||||||
|
# objects are deleted when the source object is deleted.
|
||||||
|
# Original list of tags:
|
||||||
|
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||||
|
[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('hairy', <ContentType: animal>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2), ('yellow', <ContentType: animal>, 1)]
|
||||||
|
|
||||||
|
>>> lion.delete()
|
||||||
|
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||||
|
[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
|
||||||
|
|
||||||
|
# If Generic Relation is not explicitly defined, any related objects
|
||||||
|
# remain after deletion of the source object.
|
||||||
|
>>> quartz.delete()
|
||||||
|
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||||
|
[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
|
||||||
|
|
||||||
|
# If you delete a tag, the objects using the tag are unaffected
|
||||||
|
# (other than losing a tag)
|
||||||
|
>>> tag = TaggedItem.objects.get(id=1)
|
||||||
|
>>> tag.delete()
|
||||||
|
>>> bacon.tags.all()
|
||||||
|
[<TaggedItem: salty>]
|
||||||
|
>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
|
||||||
|
[('clearish', <ContentType: mineral>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
|
||||||
|
|
||||||
"""}
|
"""}
|
||||||
|
|
Loading…
Reference in New Issue