From 5922dabea0e622af3f33d17566363a74ff533cd1 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Thu, 1 May 2008 00:32:50 +0000 Subject: [PATCH] gis: Merged revisions 7499,7501-7502,7504,7509-7510 via svnmerge from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@7511 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/base.py | 26 ------------------------- django/db/models/query.py | 17 +++++++++++++--- django/db/models/sql/query.py | 21 +++++++++++++++++++- django/db/models/sql/where.py | 6 +++--- docs/db-api.txt | 23 ++++++++++++++++++++++ docs/request_response.txt | 23 +++++++++++++++++++++- tests/modeltests/basic/models.py | 12 ++++++++++++ tests/regressiontests/queries/models.py | 8 ++++---- 8 files changed, 98 insertions(+), 38 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index bb02d7a00c..181d845932 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -237,32 +237,6 @@ class Model(object): raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0] dispatcher.send(signal=signals.post_init, sender=self.__class__, instance=self) - def from_sequence(cls, values): - """ - An alternate class constructor, primarily for internal use. - - Creates a model instance from a sequence of values (which corresponds - to all the non-many-to-many fields in creation order. If there are more - fields than values, the remaining (final) fields are given their - default values. - - ForeignKey fields can only be initialised using id values, not - instances, in this method. - """ - dispatcher.send(signal=signals.pre_init, sender=cls, args=values, - kwargs={}) - obj = Empty() - obj.__class__ = cls - field_iter = iter(obj._meta.fields) - for val, field in izip(values, field_iter): - setattr(obj, field.attname, val) - for field in field_iter: - setattr(obj, field.attname, field.get_default()) - dispatcher.send(signal=signals.post_init, sender=cls, instance=obj) - return obj - - from_sequence = classmethod(from_sequence) - def __repr__(self): return smart_str(u'<%s: %s>' % (self.__class__.__name__, unicode(self))) diff --git a/django/db/models/query.py b/django/db/models/query.py index 3011b9d9a6..6b341ba9ab 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -28,6 +28,17 @@ class QuerySet(object): # PYTHON MAGIC METHODS # ######################## + def __getstate__(self): + """ + Allows the Queryset to be pickled. + """ + # Force the cache to be fully populated. + len(self) + + obj_dict = self.__dict__.copy() + obj_dict['_iter'] = None + return obj_dict + def __repr__(self): return repr(list(self)) @@ -37,7 +48,7 @@ class QuerySet(object): # whilst not messing up any existing iterators against the queryset. if self._result_cache is None: if self._iter: - self._result_cache = list(self._iter()) + self._result_cache = list(self._iter) else: self._result_cache = list(self.iterator()) elif self._iter: @@ -153,7 +164,7 @@ class QuerySet(object): obj, _ = get_cached_row(self.model, row, index_start, max_depth, requested=requested) else: - obj = self.model.from_sequence(row[index_start:]) + obj = self.model(*row[index_start:]) for i, k in enumerate(extra_select): setattr(obj, k, row[i]) yield obj @@ -644,7 +655,7 @@ def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0, restricted = requested is not None index_end = index_start + len(klass._meta.fields) - obj = klass.from_sequence(row[index_start:index_end]) + obj = klass(*row[index_start:index_end]) for f in klass._meta.fields: if (not f.rel or (not restricted and f.null) or (restricted and f.name not in requested) or f.rel.parent_link): diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index bfed984953..a6957bab7b 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -99,6 +99,24 @@ class Query(object): memo[id(self)] = result return result + def __getstate__(self): + """ + Pickling support. + """ + obj_dict = self.__dict__.copy() + del obj_dict['connection'] + return obj_dict + + def __setstate__(self, obj_dict): + """ + Unpickling support. + """ + self.__dict__.update(obj_dict) + # XXX: Need a better solution for this when multi-db stuff is + # supported. It's the only class-reference to the module-level + # connection variable. + self.connection = connection + def get_meta(self): """ Returns the Options instance (the model._meta) from which to start @@ -376,7 +394,8 @@ class Query(object): some cases to avoid ambiguitity with nested queries. """ qn = self.quote_name_unless_alias - result = ['(%s) AS %s' % (col, alias) for alias, col in self.extra_select.iteritems()] + qn2 = self.connection.ops.quote_name + result = ['(%s) AS %s' % (col, qn2(alias)) for alias, col in self.extra_select.iteritems()] aliases = set(self.extra_select.keys()) if with_aliases: col_aliases = aliases.copy() diff --git a/django/db/models/sql/where.py b/django/db/models/sql/where.py index c8857a01fe..3e8bfed087 100644 --- a/django/db/models/sql/where.py +++ b/django/db/models/sql/where.py @@ -51,12 +51,12 @@ class WhereNode(tree.Node): format = '(%s)' elif isinstance(child, tree.Node): sql, params = self.as_sql(child, qn) - if len(child.children) == 1: + if child.negated: + format = 'NOT (%s)' + elif len(child.children) == 1: format = '%s' else: format = '(%s)' - if child.negated: - format = 'NOT %s' % format else: sql, params = self.make_atom(child, qn) format = '%s' diff --git a/docs/db-api.txt b/docs/db-api.txt index 6299f3497d..405ed87cef 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -376,6 +376,29 @@ You can evaluate a ``QuerySet`` in the following ways: iterating over a ``QuerySet`` will take advantage of your database to load data and instantiate objects only as you need them. + +Pickling QuerySets +~~~~~~~~~~~~~~~~~~ + +If you pickle_ a ``QuerySet``, this will also force all the results to be +loaded into memory prior to pickling. This is because pickling is usually used +as a precursor to caching and when the cached queryset is reloaded, you want +the results to already be present. This means that when you unpickle a +``QuerySet``, it contains the results at the moment it was pickled, rather +than the results that are currently in the database. + +If you only want to pickle the necessary information to recreate the +``Queryset`` from the database at a later time, pickle the ``query`` attribute +of the ``QuerySet``. You can then recreate the original ``QuerySet`` (without +any results loaded) using some code like this:: + + >>> import pickle + >>> query = pickle.loads(s) # Assuming 's' is the pickled string. + >>> qs = MyModel.objects.all() + >>> qs.query = query # Restore the original 'query'. + +.. _pickle: http://docs.python.org/lib/module-pickle.html + Limiting QuerySets ------------------ diff --git a/docs/request_response.txt b/docs/request_response.txt index a4f03b9185..866a697e31 100644 --- a/docs/request_response.txt +++ b/docs/request_response.txt @@ -402,6 +402,27 @@ hard-coded strings. If you use this technique, follow these guidelines: content, you can't use the ``HttpResponse`` instance as a file-like object. Doing so will raise ``Exception``. +Setting headers +~~~~~~~~~~~~~~~ + +To set a header in your response, just treat it like a dictionary:: + + >>> response = HttpResponse() + >>> response['Pragma'] = 'no-cache' + +Telling the browser to treat the response as a file attachment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To tell the browser to treat the response as a file attachment, use the +``mimetype`` argument and set the ``Content-Disposition`` header. For example, +this is how you might return a Microsoft Excel spreadsheet:: + + >>> response = HttpResponse(my_data, mimetype='application/vnd.ms-excel') + >>> response['Content-Disposition'] = 'attachment; filename=foo.xls' + +There's nothing Django-specific about the ``Content-Disposition`` header, but +it's easy to forget the syntax, so we've included it here. + Methods ------- @@ -420,7 +441,7 @@ Methods but since this is actually the value included in the HTTP ``Content-Type`` header, it can also include the character set encoding, which makes it more than just a MIME type specification. If ``mimetype`` is specified - (not None), that value is used. Otherwise, ``content_type`` is used. If + (not ``None``), that value is used. Otherwise, ``content_type`` is used. If neither is given, the ``DEFAULT_CONTENT_TYPE`` setting is used. ``__setitem__(header, value)`` diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index 51de8a50f8..d7c27cb15b 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -398,4 +398,16 @@ u'\u6797\u539f \u3081\u3050\u307f' >>> s = set([a10, a11, a12]) >>> Article.objects.get(headline='Article 11') in s True + +# The 'select' argument to extra() supports names with dashes in them, as long +# as you use values(). +>>> Article.objects.filter(pub_date__year=2008).extra(select={'dashed-value': '1'}).values('headline', 'dashed-value') +[{'headline': u'Article 11', 'dashed-value': 1}, {'headline': u'Article 12', 'dashed-value': 1}] + +# If you use 'select' with extra() and names containing dashes on a query +# that's *not* a values() query, those extra 'select' values will silently be +# ignored. +>>> articles = Article.objects.filter(pub_date__year=2008).extra(select={'dashed-value': '1', 'undashedvalue': '2'}) +>>> articles[0].undashedvalue +2 """ diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py index 6fd361ec19..5beaf5fb09 100644 --- a/tests/regressiontests/queries/models.py +++ b/tests/regressiontests/queries/models.py @@ -120,13 +120,13 @@ class LoopZ(models.Model): # 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') + qs = super(CustomManager, self).get_query_set() + return qs.filter(is_public=True, tag__name='t1') class ManagedModel(models.Model): data = models.CharField(max_length=10) tag = models.ForeignKey(Tag) - public = models.BooleanField(default=True) + is_public = models.BooleanField(default=True) objects = CustomManager() normal_manager = models.Manager() @@ -698,7 +698,7 @@ More twisted cases, involving nested negations. 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) +>>> mm = ManagedModel.objects.create(data='mm1', tag=t1, is_public=True) >>> ManagedModel.objects.update(data='mm') """}