From 61aadb890f7e7bb41cf2df35d25d32d6e8ae4429 Mon Sep 17 00:00:00 2001
From: Derek Willis <dwillis@gmail.com>
Date: Mon, 22 Mar 2010 22:01:15 -0400
Subject: [PATCH] QuerySet.delete() returns number deleted
---
django/contrib/sessions/tests.py | 3 +++
django/db/models/query.py | 3 +++
docs/topics/db/queries.txt | 6 ++++--
tests/modeltests/basic/models.py | 1 +
tests/modeltests/generic_relations/models.py | 3 ++-
tests/modeltests/many_to_many/models.py | 2 ++
tests/modeltests/many_to_one/models.py | 1 +
tests/modeltests/proxy_models/models.py | 1 +
.../custom_managers_regress/models.py | 1 +
.../regressiontests/m2m_through_regress/models.py | 2 ++
.../model_inheritance_regress/models.py | 2 +-
tests/regressiontests/model_regress/models.py | 1 +
12 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py
index f0a3c4e..6dbf676 100644
|
a
|
b
|
True
|
| 51 | 51 | # Submitting an invalid session key (either by guessing, or if the db has |
| 52 | 52 | # removed the key) results in a new key being generated. |
| 53 | 53 | >>> Session.objects.filter(pk=db_session.session_key).delete() |
| | 54 | 1 |
| 54 | 55 | >>> db_session = DatabaseSession(db_session.session_key) |
| 55 | 56 | >>> db_session.save() |
| 56 | 57 | >>> DatabaseSession('1').get('cat') |
| … |
… |
False
|
| 126 | 127 | True |
| 127 | 128 | |
| 128 | 129 | >>> Session.objects.filter(pk=file_session.session_key).delete() |
| | 130 | 0 |
| 129 | 131 | >>> file_session = FileSession(file_session.session_key) |
| 130 | 132 | >>> file_session.save() |
| 131 | 133 | |
| … |
… |
True
|
| 189 | 191 | True |
| 190 | 192 | |
| 191 | 193 | >>> Session.objects.filter(pk=cache_session.session_key).delete() |
| | 194 | 0 |
| 192 | 195 | >>> cache_session = CacheSession(cache_session.session_key) |
| 193 | 196 | >>> cache_session.save() |
| 194 | 197 | >>> cache_session.delete(cache_session.session_key) |
diff --git a/django/db/models/query.py b/django/db/models/query.py
index f6b4419..c123cfd 100644
|
a
|
b
|
class QuerySet(object):
|
| 428 | 428 | # Delete objects in chunks to prevent the list of related objects from |
| 429 | 429 | # becoming too long. |
| 430 | 430 | seen_objs = None |
| | 431 | count = 0 |
| 431 | 432 | while 1: |
| 432 | 433 | # Collect all the objects to be deleted in this chunk, and all the |
| 433 | 434 | # objects that are related to the objects that are to be deleted. |
| 434 | 435 | seen_objs = CollectedObjects(seen_objs) |
| 435 | 436 | for object in del_query[:CHUNK_SIZE]: |
| 436 | 437 | object._collect_sub_objects(seen_objs) |
| | 438 | count += 1 |
| 437 | 439 | |
| 438 | 440 | if not seen_objs: |
| 439 | 441 | break |
| … |
… |
class QuerySet(object):
|
| 441 | 443 | |
| 442 | 444 | # Clear the result cache, in case this QuerySet gets reused. |
| 443 | 445 | self._result_cache = None |
| | 446 | return count |
| 444 | 447 | delete.alters_data = True |
| 445 | 448 | |
| 446 | 449 | def update(self, **kwargs): |
diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt
index 286a0c4..7389531 100644
|
a
|
b
|
deletes the object and has no return value. Example::
|
| 729 | 729 | e.delete() |
| 730 | 730 | |
| 731 | 731 | You can also delete objects in bulk. Every ``QuerySet`` has a ``delete()`` |
| 732 | | method, which deletes all members of that ``QuerySet``. |
| | 732 | method, which deletes all members of that ``QuerySet`` and returns the number of |
| | 733 | items deleted. |
| 733 | 734 | |
| 734 | 735 | For example, this deletes all ``Entry`` objects with a ``pub_date`` year of |
| 735 | 736 | 2005:: |
| 736 | 737 | |
| 737 | | Entry.objects.filter(pub_date__year=2005).delete() |
| | 738 | >>> Entry.objects.filter(pub_date__year=2005).delete() |
| | 739 | 42 |
| 738 | 740 | |
| 739 | 741 | Keep in mind that this will, whenever possible, be executed purely in |
| 740 | 742 | SQL, and so the ``delete()`` methods of individual object instances |
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py
index c86cb3a..f278757 100644
|
a
|
b
|
AttributeError: Manager isn't accessible via Article instances
|
| 365 | 365 | >>> Article.objects.all() |
| 366 | 366 | [<Article: Area woman programs in Python>, <Article: Second article>, <Article: Third article>, <Article: Article 6>, <Article: Default headline>, <Article: Fourth article>, <Article: Article 7>, <Article: Updated article 8>] |
| 367 | 367 | >>> Article.objects.filter(id__lte=4).delete() |
| | 368 | 4 |
| 368 | 369 | >>> Article.objects.all() |
| 369 | 370 | [<Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>] |
| 370 | 371 | """} |
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index 1bc6577..075eab0 100644
|
a
|
b
|
__test__ = {'API_TESTS':"""
|
| 167 | 167 | [(u'clearish', <ContentType: mineral>, 1), (u'fatty', <ContentType: animal>, 1), (u'salty', <ContentType: vegetable>, 2), (u'shiny', <ContentType: animal>, 1)] |
| 168 | 168 | |
| 169 | 169 | >>> TaggedItem.objects.filter(tag='fatty').delete() |
| 170 | | |
| | 170 | 1 |
| 171 | 171 | >>> ctype = ContentType.objects.get_for_model(lion) |
| 172 | 172 | >>> Animal.objects.filter(tags__content_type=ctype) |
| 173 | 173 | [<Animal: Platypus>] |
| … |
… |
__test__ = {'API_TESTS':"""
|
| 197 | 197 | # Filtering and deleting works |
| 198 | 198 | >>> subjective = ["cooler"] |
| 199 | 199 | >>> tiger.comparisons.filter(comparative__in=subjective).delete() |
| | 200 | 2 |
| 200 | 201 | >>> Comparison.objects.all() |
| 201 | 202 | [<Comparison: cheetah is faster than tiger>, <Comparison: tiger is stronger than cheetah>] |
| 202 | 203 | |
diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py
index 21b4e82..a4a9c8d 100644
|
a
|
b
|
TypeError: 'Publication' instance expected
|
| 243 | 243 | |
| 244 | 244 | # Bulk delete some Publications - references to deleted publications should go |
| 245 | 245 | >>> Publication.objects.filter(title__startswith='Science').delete() |
| | 246 | 2 |
| 246 | 247 | >>> Publication.objects.all() |
| 247 | 248 | [<Publication: Highlights for Children>, <Publication: The Python Journal>] |
| 248 | 249 | >>> Article.objects.all() |
| … |
… |
TypeError: 'Publication' instance expected
|
| 255 | 256 | >>> print q |
| 256 | 257 | [<Article: Django lets you build Web apps easily>] |
| 257 | 258 | >>> q.delete() |
| | 259 | 1 |
| 258 | 260 | |
| 259 | 261 | # After the delete, the QuerySet cache needs to be cleared, and the referenced objects should be gone |
| 260 | 262 | >>> print q |
diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
index c6be981..a64dfd3 100644
|
a
|
b
|
True
|
| 292 | 292 | |
| 293 | 293 | # You can delete using a JOIN in the query. |
| 294 | 294 | >>> Reporter.objects.filter(article__headline__startswith='This').delete() |
| | 295 | 3 |
| 295 | 296 | >>> Reporter.objects.all() |
| 296 | 297 | [] |
| 297 | 298 | >>> Article.objects.all() |
diff --git a/tests/modeltests/proxy_models/models.py b/tests/modeltests/proxy_models/models.py
index 28446b9..b2e4e8f 100644
|
a
|
b
|
FieldError: Proxy model 'NoNewFields' contains model fields.
|
| 264 | 264 | # Manager tests. |
| 265 | 265 | |
| 266 | 266 | >>> Person.objects.all().delete() |
| | 267 | 5 |
| 267 | 268 | >>> _ = Person.objects.create(name="fred") |
| 268 | 269 | >>> _ = Person.objects.create(name="wilma") |
| 269 | 270 | >>> _ = Person.objects.create(name="barney") |
diff --git a/tests/regressiontests/custom_managers_regress/models.py b/tests/regressiontests/custom_managers_regress/models.py
index 898730e..e30eec1 100644
|
a
|
b
|
instances. This is a regression test for #8990, #9527
|
| 56 | 56 | Deleting related objects should also not be distracted by a restricted manager |
| 57 | 57 | on the related object. This is a regression test for #2698. |
| 58 | 58 | >>> RestrictedModel.plain_manager.all().delete() |
| | 59 | 1 |
| 59 | 60 | >>> for name, public in (('one', True), ('two', False), ('three', False)): |
| 60 | 61 | ... _ = RestrictedModel.objects.create(name=name, is_public=public, related=related) |
| 61 | 62 | |
diff --git a/tests/regressiontests/m2m_through_regress/models.py b/tests/regressiontests/m2m_through_regress/models.py
index 56aecd6..0c25775 100644
|
a
|
b
|
AttributeError: Cannot use create() on a ManyToManyField which specifies an inte
|
| 126 | 126 | # one for each end of the m2m, plus the through model. |
| 127 | 127 | |
| 128 | 128 | >>> User.objects.all().delete() |
| | 129 | 3 |
| 129 | 130 | >>> UserMembership.objects.all().delete() |
| | 131 | 0 |
| 130 | 132 | >>> frank.delete() |
| 131 | 133 | >>> rock.delete() |
| 132 | 134 | >>> jim.delete() |
diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/regressiontests/model_inheritance_regress/models.py
index c669b23..a2f3f1b 100644
|
a
|
b
|
True
|
| 239 | 239 | |
| 240 | 240 | # This should delete both Restuarants, plus the related places, plus the ItalianRestaurant. |
| 241 | 241 | >>> Restaurant.objects.all().delete() |
| 242 | | |
| | 242 | 2 |
| 243 | 243 | >>> Place.objects.get(pk=ident) |
| 244 | 244 | Traceback (most recent call last): |
| 245 | 245 | ... |
diff --git a/tests/regressiontests/model_regress/models.py b/tests/regressiontests/model_regress/models.py
index 420f2c2..2947841 100644
|
a
|
b
|
if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] not in (
|
| 159 | 159 | # The idea is that all these creations and saving should work without crashing. |
| 160 | 160 | # It's not rocket science. |
| 161 | 161 | >>> Article.objects.all().delete() |
| | 162 | 2 |
| 162 | 163 | >>> dt1 = datetime.datetime(2008, 8, 31, 16, 20, tzinfo=tzinfo.FixedOffset(600)) |
| 163 | 164 | >>> dt2 = datetime.datetime(2008, 8, 31, 17, 20, tzinfo=tzinfo.FixedOffset(600)) |
| 164 | 165 | >>> obj = Article.objects.create(headline="A headline", pub_date=dt1, article_text="foo") |