summaryrefslogtreecommitdiff
path: root/tests/modeltests/delete/models.py
blob: 49aab1fb1bc826bb73099dcf9738aaac3972b26e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# coding: utf-8
"""
Tests for some corner cases with deleting.
"""

from django.db import models

class DefaultRepr(object):
    def __repr__(self):
        return u"<%s: %s>" % (self.__class__.__name__, self.__dict__)

class A(DefaultRepr, models.Model):
    pass

class B(DefaultRepr, models.Model):
    a = models.ForeignKey(A)

class C(DefaultRepr, models.Model):
    b = models.ForeignKey(B)

class D(DefaultRepr, models.Model):
    c = models.ForeignKey(C)
    a = models.ForeignKey(A)

# Simplified, we have:
# A
# B -> A
# C -> B
# D -> C
# D -> A

# So, we must delete Ds first of all, then Cs then Bs then As.
# However, if we start at As, we might find Bs first (in which 
# case things will be nice), or find Ds first.

# Some mutually dependent models, but nullable
class E(DefaultRepr, models.Model):
    f = models.ForeignKey('F', null=True, related_name='e_rel')

class F(DefaultRepr, models.Model):
    e = models.ForeignKey(E, related_name='f_rel')


__test__ = {'API_TESTS': """
### Tests for models A,B,C,D ###

## First, test the CollectedObjects data structure directly

>>> from django.db.models.query import CollectedObjects

>>> g = CollectedObjects()
>>> g.add("key1", 1, "item1", None)
False
>>> g["key1"]
{1: 'item1'}
>>> g.add("key2", 1, "item1", "key1")
False
>>> g.add("key2", 2, "item2", "key1")
False
>>> g["key2"]
{1: 'item1', 2: 'item2'}
>>> g.add("key3", 1, "item1", "key1")
False
>>> g.add("key3", 1, "item1", "key2")
True
>>> g.ordered_keys()
['key3', 'key2', 'key1']

>>> g.add("key2", 1, "item1", "key3")
True
>>> g.ordered_keys()
Traceback (most recent call last):
    ...
CyclicDependency: There is a cyclic dependency of items to be processed.


## Second, test the usage of CollectedObjects by Model.delete()

# Due to the way that transactions work in the test harness,
# doing m.delete() here can work but fail in a real situation,
# since it may delete all objects, but not in the right order.
# So we manually check that the order of deletion is correct.

# Also, it is possible that the order is correct 'accidentally', due
# solely to order of imports etc.  To check this, we set the order
# that 'get_models()' will retrieve to a known 'nice' order, and
# then try again with a known 'tricky' order.  Slightly naughty
# access to internals here :-)

# If implementation changes, then the tests may need to be simplified:
#  - remove the lines that set the .keyOrder and clear the related
#    object caches
#  - remove the second set of tests (with a2, b2 etc)

>>> from django.db.models.loading import cache

>>> def clear_rel_obj_caches(models):
...     for m in models:
...         if hasattr(m._meta, '_related_objects_cache'): 
...             del m._meta._related_objects_cache

# Nice order
>>> cache.app_models['delete'].keyOrder = ['a', 'b', 'c', 'd']
>>> clear_rel_obj_caches([A, B, C, D])

>>> a1 = A()
>>> a1.save()
>>> b1 = B(a=a1)
>>> b1.save()
>>> c1 = C(b=b1)
>>> c1.save()
>>> d1 = D(c=c1, a=a1)
>>> d1.save()

>>> o = CollectedObjects()
>>> a1._collect_sub_objects(o)
>>> o.keys()
[<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>]
>>> a1.delete()

# Same again with a known bad order
>>> cache.app_models['delete'].keyOrder = ['d', 'c', 'b', 'a']
>>> clear_rel_obj_caches([A, B, C, D])

>>> a2 = A()
>>> a2.save()
>>> b2 = B(a=a2)
>>> b2.save()
>>> c2 = C(b=b2)
>>> c2.save()
>>> d2 = D(c=c2, a=a2)
>>> d2.save()

>>> o = CollectedObjects()
>>> a2._collect_sub_objects(o)
>>> o.keys()
[<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>]
>>> a2.delete()

### Tests for models E,F - nullable related fields ###

## First, test the CollectedObjects data structure directly

>>> g = CollectedObjects()
>>> g.add("key1", 1, "item1", None)
False
>>> g.add("key2", 1, "item1", "key1", nullable=True)
False
>>> g.add("key1", 1, "item1", "key2")
True
>>> g.ordered_keys()
['key1', 'key2']

## Second, test the usage of CollectedObjects by Model.delete()

>>> e1 = E()
>>> e1.save()
>>> f1 = F(e=e1)
>>> f1.save()
>>> e1.f = f1
>>> e1.save()

# Since E.f is nullable, we should delete F first (after nulling out
# the E.f field), then E.

>>> o = CollectedObjects()
>>> e1._collect_sub_objects(o)
>>> o.keys()
[<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>]

>>> e1.delete()

>>> e2 = E()
>>> e2.save()
>>> f2 = F(e=e2)
>>> f2.save()
>>> e2.f = f2
>>> e2.save()

# Same deal as before, though we are starting from the other object.

>>> o = CollectedObjects()
>>> f2._collect_sub_objects(o)
>>> o.keys()
[<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>]

>>> f2.delete()

"""
}