summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Bronn <jbronn@gmail.com>2008-04-28 14:08:46 +0000
committerJustin Bronn <jbronn@gmail.com>2008-04-28 14:08:46 +0000
commite5b52f90f0ca2b3b3490c22d869f8c2afc14a526 (patch)
tree37712fea84257f0c35b400571205face5a4c4ca8
parenta125a1c9b646368702bb14f26c1a6b09cff3e5a0 (diff)
gis: Merged revisions 7485-7491,7493-7497 via svnmerge from trunk.
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7498 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/contrib/admin/views/main.py19
-rw-r--r--django/db/backends/__init__.py1
-rw-r--r--django/db/backends/mysql/base.py1
-rw-r--r--django/db/backends/mysql_old/base.py1
-rw-r--r--django/db/models/fields/__init__.py4
-rw-r--r--django/db/models/query.py4
-rw-r--r--django/db/models/sql/constants.py2
-rw-r--r--django/db/models/sql/query.py17
-rw-r--r--django/db/models/sql/subqueries.py26
-rw-r--r--docs/db-api.txt26
-rw-r--r--tests/modeltests/many_to_one/models.py17
-rw-r--r--tests/regressiontests/queries/models.py47
12 files changed, 119 insertions, 46 deletions
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 2b7a690a83..515dea56a1 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -716,24 +716,9 @@ class ChangeList(object):
qs = qs.select_related()
break
- # Calculate lookup_order_field.
- # If the order-by field is a field with a relationship, order by the
- # value in the related table.
- lookup_order_field = self.order_field
- try:
- f = self.lookup_opts.get_field(self.order_field, many_to_many=False)
- except models.FieldDoesNotExist:
- pass
- else:
- if isinstance(f.rel, models.OneToOneRel):
- # For OneToOneFields, don't try to order by the related object's ordering criteria.
- pass
- elif isinstance(f.rel, models.ManyToOneRel):
- rel_ordering = f.rel.to._meta.ordering and f.rel.to._meta.ordering[0] or f.rel.to._meta.pk.column
- lookup_order_field = '%s.%s' % (f.rel.to._meta.db_table, rel_ordering)
-
# Set ordering.
- qs = qs.order_by((self.order_type == 'desc' and '-' or '') + lookup_order_field)
+ if self.order_field:
+ qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field))
# Apply keyword searches.
def construct_search(field_name):
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 8917fc3b23..063c0ae411 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -51,6 +51,7 @@ class BaseDatabaseFeatures(object):
uses_case_insensitive_names = False
uses_custom_query_class = False
empty_fetchmany_value = []
+ update_can_self_select = True
class BaseDatabaseOperations(object):
"""
diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
index 17aa6f13bf..da57ecbe47 100644
--- a/django/db/backends/mysql/base.py
+++ b/django/db/backends/mysql/base.py
@@ -63,6 +63,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False
inline_fk_references = False
empty_fetchmany_value = ()
+ update_can_self_select = False
class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):
diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py
index efbfeeafc5..0ae114d176 100644
--- a/django/db/backends/mysql_old/base.py
+++ b/django/db/backends/mysql_old/base.py
@@ -67,6 +67,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
autoindexes_primary_keys = False
inline_fk_references = False
empty_fetchmany_value = ()
+ update_can_self_select = False
class DatabaseOperations(BaseDatabaseOperations):
def date_extract_sql(self, lookup_type, field_name):
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index 3c9169d53b..bb2191e3a4 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -9,6 +9,7 @@ except ImportError:
from django.db import get_creation_module
from django.db.models import signals
+from django.db.models.query_utils import QueryWrapper
from django.dispatch import dispatcher
from django.conf import settings
from django.core import validators
@@ -226,6 +227,9 @@ class Field(object):
def get_db_prep_lookup(self, lookup_type, value):
"Returns field's value prepared for database lookup."
+ if hasattr(value, 'as_sql'):
+ sql, params = value.as_sql()
+ return QueryWrapper(('(%s)' % sql), params)
if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'):
return [value]
elif lookup_type in ('range', 'in'):
diff --git a/django/db/models/query.py b/django/db/models/query.py
index e1e2bb19f2..3011b9d9a6 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -284,6 +284,7 @@ class QuerySet(object):
query = self.query.clone(sql.UpdateQuery)
query.add_update_values(kwargs)
query.execute_sql(None)
+ transaction.commit_unless_managed()
self._result_cache = None
update.alters_data = True
@@ -496,9 +497,6 @@ class ValuesQuerySet(QuerySet):
# QuerySet.clone() will also set up the _fields attribute with the
# names of the model fields to select.
- def __iter__(self):
- return self.iterator()
-
def iterator(self):
self.query.trim_extra_select(self.extra_names)
names = self.query.extra_select.keys() + self.field_names
diff --git a/django/db/models/sql/constants.py b/django/db/models/sql/constants.py
index 3075817385..129a592b31 100644
--- a/django/db/models/sql/constants.py
+++ b/django/db/models/sql/constants.py
@@ -28,7 +28,7 @@ NULLABLE = 6
MULTI = 'multi'
SINGLE = 'single'
-ORDER_PATTERN = re.compile(r'\?|[-+]?\w+$')
+ORDER_PATTERN = re.compile(r'\?|[-+]?[.\w]+$')
ORDER_DIR = {
'ASC': ('ASC', 'DESC'),
'DESC': ('DESC', 'ASC')}
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 59b2ebdd68..bfed984953 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -895,9 +895,15 @@ class Query(object):
Add a single filter to the query. The 'filter_expr' is a pair:
(filter_string, value). E.g. ('name__contains', 'fred')
- If 'negate' is True, this is an exclude() filter. If 'trim' is True, we
- automatically trim the final join group (used internally when
- constructing nested queries).
+ If 'negate' is True, this is an exclude() filter. It's important to
+ note that this method does not negate anything in the where-clause
+ object when inserting the filter constraints. This is because negated
+ filters often require multiple calls to add_filter() and the negation
+ should only happen once. So the caller is responsible for this (the
+ caller will normally be add_q(), so that as an example).
+
+ If 'trim' is True, we automatically trim the final join group (used
+ internally when constructing nested queries).
If 'can_reuse' is a set, we are processing a component of a
multi-component filter (e.g. filter(Q1, Q2)). In this case, 'can_reuse'
@@ -1001,7 +1007,6 @@ class Query(object):
self.where.add((alias, col, field, lookup_type, value), connector)
if negate:
- self.where.negate()
for alias in join_list:
self.promote_alias(alias)
if final > 1 and lookup_type != 'isnull':
@@ -1039,12 +1044,12 @@ class Query(object):
self.where.start_subtree(connector)
self.add_q(child, used_aliases)
self.where.end_subtree()
- if q_object.negated:
- self.where.children[-1].negate()
else:
self.add_filter(child, connector, q_object.negated,
can_reuse=used_aliases)
connector = q_object.connector
+ if q_object.negated:
+ self.where.negate()
if subtree:
self.where.end_subtree()
diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
index 382e6e94ff..7385cd00e1 100644
--- a/django/db/models/sql/subqueries.py
+++ b/django/db/models/sql/subqueries.py
@@ -95,7 +95,7 @@ class UpdateQuery(Query):
def _setup_query(self):
"""
- Runs on initialisation and after cloning. Any attributes that would
+ Runs on initialization and after cloning. Any attributes that would
normally be set in __init__ should go in here, instead, so that they
are also set up after a clone() call.
"""
@@ -159,20 +159,37 @@ class UpdateQuery(Query):
# from other tables.
query = self.clone(klass=Query)
query.bump_prefix()
- query.select = []
query.extra_select = {}
- query.add_fields([query.model._meta.pk.name])
+ first_table = query.tables[0]
+ if query.alias_refcount[first_table] == 1:
+ # We can remove one table from the inner query.
+ query.unref_alias(first_table)
+ for i in xrange(1, len(query.tables)):
+ table = query.tables[i]
+ if query.alias_refcount[table]:
+ break
+ join_info = query.alias_map[table]
+ query.select = [(join_info[RHS_ALIAS], join_info[RHS_JOIN_COL])]
+ must_pre_select = False
+ else:
+ query.select = []
+ query.add_fields([query.model._meta.pk.name])
+ must_pre_select = not self.connection.features.update_can_self_select
# Now we adjust the current query: reset the where clause and get rid
# of all the tables we don't need (since they're in the sub-select).
self.where = self.where_class()
- if self.related_updates:
+ if self.related_updates or must_pre_select:
+ # Either we're using the idents in multiple update queries (so
+ # don't want them to change), or the db backend doesn't support
+ # selecting from the updating table (e.g. MySQL).
idents = []
for rows in query.execute_sql(MULTI):
idents.extend([r[0] for r in rows])
self.add_filter(('pk__in', idents))
self.related_ids = idents
else:
+ # The fast path. Filters and updates in one query.
self.add_filter(('pk__in', query))
for alias in self.tables[1:]:
self.alias_refcount[alias] = 0
@@ -349,6 +366,7 @@ class DateQuery(Query):
self.connection.ops.date_trunc_sql)
self.select = [select]
self.select_fields = [None]
+ self.select_related = False # See #7097.
self.distinct = True
self.order_by = order == 'ASC' and [1] or [-1]
diff --git a/docs/db-api.txt b/docs/db-api.txt
index 6585890c72..6299f3497d 100644
--- a/docs/db-api.txt
+++ b/docs/db-api.txt
@@ -527,7 +527,7 @@ applied to a query, not even the default ordering, call ``order_by()`` with no
parameters.
**New in Django development version:** The syntax for ordering across related
-models has changed. See the `Django 0.96 documentation`_ for the old behaviour.
+models has changed. See the `Django 0.96 documentation`_ for the old behavior.
.. _Django 0.96 documentation: http://www.djangoproject.com/documentation/0.96/model-api/#floatfield
@@ -540,9 +540,9 @@ backend normally orders them.
**New in Django development version**
-If you want to reverse the order in which a queryset's elements are returned,
-you can use the ``reverse()`` method. Calling ``reverse()`` a second time
-restores the ordering back to the normal direction.
+Use the ``reverse()`` method to reverse the order in which a queryset's
+elements are returned. Calling ``reverse()`` a second time restores the
+ordering back to the normal direction.
To retrieve the ''last'' five items in a queryset, you could do this::
@@ -552,7 +552,7 @@ Note that this is not quite the same as slicing from the end of a sequence in
Python. The above example will return the last item first, then the
penultimate item and so on. If we had a Python sequence and looked at
``seq[:-5]``, we would see the fifth-last item first. Django doesn't support
-that mode of access (slicing from the end), since it is not possible to do it
+that mode of access (slicing from the end), because it's not possible to do it
efficiently in SQL.
``distinct()``
@@ -1660,7 +1660,7 @@ entry. The entries select by the second filter may or may not be the same as
the entries in the first filter. We are filtering the ``Blog`` items with each
filter statement, not the ``Entry`` items.
-All of this behaviour also applies to ``exclude()``: all the conditions in a
+All of this behavior also applies to ``exclude()``: all the conditions in a
single ``exclude()`` statement apply to a single instance (if those conditions
are talking about the same multi-valued relation). Conditions in subsequent
``filter()`` or ``exclude()`` calls that refer to the same relation may end up
@@ -2101,24 +2101,24 @@ Updating multiple objects at once
**New in Django development version**
Sometimes you want to set a field to a particular value for all the objects in
-a queryset. You can do this with the ``update()`` method. For example::
+a ``QuerySet``. You can do this with the ``update()`` method. For example::
- # Update all the headlines to the same value.
- Entry.objects.all().update(headline='Everything is the same')
+ # Update all the headlines with pub_date in 2007.
+ Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
You can only set non-relation fields and ``ForeignKey`` fields using this
-method and the value you set the field to must be a normal Python value (you
-can't set a field to be equal to some other field at the moment).
+method, and the value you set the field to must be a hard-coded Python value
+(i.e., you can't set a field to be equal to some other field at the moment).
To update ``ForeignKey`` fields, set the new value to be the new model
instance you want to point to. Example::
b = Blog.objects.get(pk=1)
- # Make all entries belong to this blog.
+ # Change every Entry so that it belongs to this Blog.
Entry.objects.all().update(blog=b)
The ``update()`` method is applied instantly and doesn't return anything
-(similar to ``delete()``). The only restriction on the queryset that is
+(similar to ``delete()``). The only restriction on the ``QuerySet`` that is
updated is that it can only access one database table, the model's main
table. So don't try to filter based on related fields or anything like that;
it won't work.
diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py
index 6616f8b55e..53ad4466bb 100644
--- a/tests/modeltests/many_to_one/models.py
+++ b/tests/modeltests/many_to_one/models.py
@@ -246,7 +246,7 @@ FieldError: Cannot resolve keyword 'reporter_id' into field. Choices are: headli
>>> Reporter.objects.filter(article__reporter__exact=r).distinct()
[<Reporter: John Smith>]
-# Check that implied __exact also works
+# Check that implied __exact also works.
>>> Reporter.objects.filter(article__reporter=r).distinct()
[<Reporter: John Smith>]
@@ -266,11 +266,24 @@ True
>>> Reporter.objects.order_by('first_name')
[<Reporter: John Smith>]
-# Deletes using a join in the query
+# You can delete using a JOIN in the query.
>>> Reporter.objects.filter(article__headline__startswith='This').delete()
>>> Reporter.objects.all()
[]
>>> Article.objects.all()
[]
+# Check that Article.objects.select_related().dates() works properly when
+# there are multiple Articles with the same date but different foreign-key
+# objects (Reporters).
+>>> r1 = Reporter.objects.create(first_name='Mike', last_name='Royko', email='royko@suntimes.com')
+>>> r2 = Reporter.objects.create(first_name='John', last_name='Kass', email='jkass@tribune.com')
+>>> a1 = Article.objects.create(headline='First', pub_date=datetime(1980, 4, 23), reporter=r1)
+>>> a2 = Article.objects.create(headline='Second', pub_date=datetime(1980, 4, 23), reporter=r2)
+>>> Article.objects.select_related().dates('pub_date', 'day')
+[datetime.datetime(1980, 4, 23, 0, 0)]
+>>> Article.objects.select_related().dates('pub_date', 'month')
+[datetime.datetime(1980, 4, 1, 0, 0)]
+>>> Article.objects.select_related().dates('pub_date', 'year')
+[datetime.datetime(1980, 1, 1, 0, 0)]
"""}
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index 483aa7218c..6fd361ec19 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -117,6 +117,24 @@ class LoopZ(models.Model):
class Meta:
ordering = ['z']
+# A model and custom default manager combination.
+class CustomManager(models.Manager):
+ def get_query_set(self):
+ return super(CustomManager, self).get_query_set().filter(public=True,
+ tag__name='t1')
+
+class ManagedModel(models.Model):
+ data = models.CharField(max_length=10)
+ tag = models.ForeignKey(Tag)
+ public = models.BooleanField(default=True)
+
+ objects = CustomManager()
+ normal_manager = models.Manager()
+
+ def __unicode__(self):
+ return self.data
+
+
__test__ = {'API_TESTS':"""
>>> t1 = Tag(name='t1')
>>> t1.save()
@@ -654,5 +672,34 @@ Bug #7045 -- extra tables used to crash SQL construction on the second use.
>>> s = qs.query.as_sql()
>>> s = qs.query.as_sql() # test passes if this doesn't raise an exception.
+Bug #7098 -- Make sure semi-deprecated ordering by related models syntax still
+works.
+>>> Item.objects.values('note__note').order_by('queries_note.note', 'id')
+[{'note__note': u'n2'}, {'note__note': u'n3'}, {'note__note': u'n3'}, {'note__note': u'n3'}]
+
+Bug #7096 -- Make sure exclude() with multiple conditions continues to work.
+>>> Tag.objects.filter(parent=t1, name='t3').order_by('name')
+[<Tag: t3>]
+>>> Tag.objects.exclude(parent=t1, name='t3').order_by('name')
+[<Tag: t1>, <Tag: t2>, <Tag: t4>, <Tag: t5>]
+>>> Item.objects.exclude(tags__name='t1', name='one').order_by('name').distinct()
+[<Item: four>, <Item: three>, <Item: two>]
+>>> Item.objects.filter(name__in=['three', 'four']).exclude(tags__name='t1').order_by('name')
+[<Item: four>, <Item: three>]
+
+More twisted cases, involving nested negations.
+>>> Item.objects.exclude(~Q(tags__name='t1', name='one'))
+[<Item: one>]
+>>> Item.objects.filter(~Q(tags__name='t1', name='one'), name='two')
+[<Item: two>]
+>>> Item.objects.exclude(~Q(tags__name='t1', name='one'), name='two')
+[<Item: four>, <Item: one>, <Item: three>]
+
+Bug #7095
+Updates that are filtered on the model being updated are somewhat tricky to get
+in MySQL. This exercises that case.
+>>> mm = ManagedModel.objects.create(data='mm1', tag=t1, public=True)
+>>> ManagedModel.objects.update(data='mm')
+
"""}