Code

Opened 5 years ago

Closed 4 years ago

Last modified 3 years ago

#9741 closed (duplicate)

generic relations: reverse chain of references not followed fully on delete

Reported by: wiswaud Owned by: aljosa
Component: Contrib apps Version: 1.0
Severity: Keywords: genericrelation genericforeignkey
Cc: eric@…, malini@…, martin@… Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

Description

If i have an object c, which has a genericforeignkey relation to b, which in turn has a genericforeignkey relation to a, and i delete a, b does get deleted, but not c. If i were to delete b, c does get deleted.

If these were foreign keys, the chain of reverse relations would get properly deleted, but for genericrelations, it only goes back 1 link.

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

class A(models.Model):
    t = models.CharField(max_length=50)
    gr_b_reverse = generic.GenericRelation('GR_B')

class FK_B(models.Model):
    """An object with a foreign key, that we'll point to an A"""
    fk = models.ForeignKey('A')

class FK_C(models.Model):
    """An object with a foreign key, that we'll point to a FK_B"""
    fk = models.ForeignKey('FK_B')

class GR_B(models.Model):
    """An object with a generic key, that we'll point to an A"""
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_obj = generic.GenericForeignKey(ct_field="content_type",
                                            fk_field="object_id")
    gr_c_reverse = generic.GenericRelation('GR_C')

class GR_C(models.Model):
    """An object with a generic key, that we'll point to a GR_B
    
    # test proper deletion of gr_c pointing to gr_b if we delete gr_b
    >>> a=A.objects.create(t='foo')
    >>> gr_b=GR_B.objects.create(content_obj=a)
    >>> gr_c=GR_C.objects.create(content_obj=gr_b)
    >>> gr_b.delete()
    >>> print A.objects.all(), GR_B.objects.all(), GR_C.objects.all()
    [<A: A object>] [] []
    >>> a.delete()
    
    # test proper deletion of gr_b pointing to a if we delete a
    >>> a=A.objects.create(t='foo')
    >>> gr_b=GR_B.objects.create(content_obj=a)
    >>> a.delete()
    >>> print A.objects.all(), GR_B.objects.all()
    [] []
    
    # test proper deletion of fk_b pointing to a, fk_c pointing to b, gr_b pointing
    # to a, and gr_c pointing to gr_b if we delete a
    >>> a=A.objects.create(t='foo')
    >>> gr_b=GR_B.objects.create(content_obj=a)
    >>> gr_c=GR_C.objects.create(content_obj=gr_b)
    >>> fk_b=FK_B.objects.create(fk=a)
    >>> fk_c=FK_C.objects.create(fk=fk_b)
    >>> print A.objects.all(), GR_B.objects.all(), GR_C.objects.all(), FK_B.objects.all(), FK_C.objects.all()
    [<A: A object>] [<GR_B: GR_B object>] [<GR_C: GR_C object>] [<FK_B: FK_B object>] [<FK_C: FK_C object>]
    >>> a.delete()
    >>> print A.objects.all(), GR_B.objects.all(), GR_C.objects.all(), FK_B.objects.all(), FK_C.objects.all()
    [] [] [] [] []
    
    # FAIL! we instead get:
    # [] [] [<GR_C: GR_C object>] [] []
    """
    
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_obj = generic.GenericForeignKey(ct_field="content_type",
                                            fk_field="object_id")

Attachments (1)

gr_test.tgz (2.7 KB) - added by wiswaud 5 years ago.
full project tree for test app

Download all attachments as: .zip

Change History (11)

Changed 5 years ago by wiswaud

full project tree for test app

comment:1 Changed 5 years ago by wiswaud

  • Cc eric@… added; eriic@… removed
  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

comment:2 Changed 5 years ago by wiswaud

test output:

eric@pluto:~/akoha/sandbox/gr_test$ ./manage.py test gr_test_app
Creating test database...
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table gr_test_app_a
Creating table gr_test_app_fk_b
Creating table gr_test_app_fk_c
Creating table gr_test_app_gr_b
Creating table gr_test_app_gr_c
Installing index for auth.Permission model
Installing index for auth.Message model
Installing index for gr_test_app.FK_B model
Installing index for gr_test_app.FK_C model
Installing index for gr_test_app.GR_B model
Installing index for gr_test_app.GR_C model
F
======================================================================
FAIL: Doctest: gr_test_app.models.GR_C
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/eric/django/django_src/django/test/_doctest.py", line 2180, in runTest
    raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for gr_test_app.models.GR_C
  File "/home/eric/akoha/sandbox/gr_test/gr_test_app/models.py", line 25, in GR_C

----------------------------------------------------------------------
File "/home/eric/akoha/sandbox/gr_test/gr_test_app/models.py", line 54, in gr_test_app.models.GR_C
Failed example:
    print A.objects.all(), GR_B.objects.all(), GR_C.objects.all(), FK_B.objects.all(), FK_C.objects.all()
Expected:
    [] [] [] [] []
Got:
    [] [] [<GR_C: GR_C object>] [] []


----------------------------------------------------------------------
Ran 1 test in 0.041s

FAILED (failures=1)
Destroying test database...

comment:3 Changed 5 years ago by wiswaud

python + django version tested with:

Python 2.5.2 (r252:60911, Jul 31 2008, 17:28:52) 
[GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> django.VERSION
(1, 1, 0, 'alpha', 0)

which is rev 9549.

also tried with (1, 0, 'final') with the exact same result.

MySQL is 5.0.51a-3ubuntu5.4

comment:4 Changed 5 years ago by jacob

  • Triage Stage changed from Unreviewed to Accepted

comment:5 Changed 5 years ago by jacob

  • milestone set to 1.1

comment:6 Changed 5 years ago by jacob

  • milestone changed from 1.1 to 1.2

Pushing to 1.2. This is borderline, but at this late stage without a patch we'll have to live with this inconsistency for now. A custom Model.delete() method can be an acceptable workaround in these cases.

comment:7 Changed 5 years ago by aljosa

  • Owner changed from nobody to aljosa

comment:8 Changed 4 years ago by mwdiers

  • Summary changed from generic relations: reverse chain of references not followed fully yon delete to generic relations: reverse chain of references not followed fully on delete

comment:9 Changed 4 years ago by carljm

  • Resolution set to duplicate
  • Status changed from new to closed

Closing as duplicate of #12953; this one was filed first, but the other already has a working patch with tests.

comment:10 Changed 3 years ago by jacob

  • milestone 1.2 deleted

Milestone 1.2 deleted

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.