Index: django/db/models/manager.py
===================================================================
--- django/db/models/manager.py	(revision 7111)
+++ django/db/models/manager.py	(working copy)
@@ -101,6 +101,9 @@
     def values(self, *args, **kwargs):
         return self.get_query_set().values(*args, **kwargs)
 
+    def fields(self, *args, **kwargs):
+        return self.get_query_set().fields(*args, **kwargs)
+
 class ManagerDescriptor(object):
     # This class ensures managers aren't accessible via model instances.
     # For example, Poll.objects works, but poll_obj.objects raises AttributeError.
Index: django/db/models/query.py
===================================================================
--- django/db/models/query.py	(revision 7111)
+++ django/db/models/query.py	(working copy)
@@ -91,6 +91,8 @@
         self._order_by = None        # Ordering, e.g. ('date', '-name'). If None, use model's ordering.
         self._select_related = False # Whether to fill cache for related objects.
         self._max_related_depth = 0  # Maximum "depth" for select_related
+        self._fields = {}            # Subset of fields of this and related models to select
+        self._modelize = True        # Convert rows to models or return dictionaries
         self._distinct = False       # Whether the query should use SELECT DISTINCT.
         self._select = {}            # Dictionary of attname -> SQL.
         self._where = []             # List of extra WHERE clauses to use.
@@ -189,7 +191,7 @@
         cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
 
         fill_cache = self._select_related
-        fields = self.model._meta.fields
+        fields = _get_model_fields(self.model._meta, "", self._fields, self._select)
         index_end = len(fields)
         has_resolve_columns = hasattr(self, 'resolve_columns')
         while 1:
@@ -201,11 +203,17 @@
                     row = self.resolve_columns(row, fields)
                 if fill_cache:
                     obj, index_end = get_cached_row(klass=self.model, row=row,
-                                                    index_start=0, max_depth=self._max_related_depth)
+                                                    index_start=0,
+                                                    max_depth=self._max_related_depth,
+                                                    fields=self._fields, modelize=self._modelize)
                 else:
-                    obj = self.model(*row[:index_end])
+                    obj = _init_model(self._modelize and self.model or None,\
+                                      fields, row[:index_end])
                 for i, k in enumerate(extra_select):
-                    setattr(obj, k[0], row[index_end+i])
+                    if self._modelize:
+                        setattr(obj, k[0], row[index_end+i])
+                    else:
+                        obj[k[0]] = row[index_end+i]
                 yield obj
 
     def count(self):
@@ -357,9 +365,6 @@
     # PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
     ##################################################
 
-    def values(self, *fields):
-        return self._clone(klass=ValuesQuerySet, _fields=fields)
-
     def dates(self, field_name, kind, order='ASC'):
         """
         Returns a list of datetime objects representing all available dates
@@ -415,6 +420,22 @@
         "Returns a new QuerySet instance with '_select_related' modified."
         return self._clone(_select_related=true_or_false, _max_related_depth=depth)
 
+    def fields(self, *fields, **related_fields):
+        """Returns a new QuerySet instance with '_fields' modified.
+        owner_fields is stored inside _fields with empty string key."""
+        assert fields or related_fields, "fields() don't have sense without arguments"
+        _fields, _select_related = _merge_fields(self.model, fields, related_fields)
+        return self._clone(_modelize=True, _fields=_fields,
+                           _select_related=_select_related or self._select_related)
+
+    def values(self, *fields, **related_fields):
+        """Returns a new QuerySet instance with '_fields' and '_modelize'."""
+        if not (fields or related_fields):
+            return self._clone(_modelize=False, _fields={}) # process all fields from master model
+        _fields, _select_related = _merge_fields(self.model, fields, related_fields)
+        return self._clone(_modelize=False, _fields=_fields,
+                           _select_related=_select_related or self._select_related)
+
     def order_by(self, *field_names):
         "Returns a new QuerySet instance with the ordering changed."
         assert self._limit is None and self._offset is None, \
@@ -448,6 +469,8 @@
         c._order_by = self._order_by
         c._select_related = self._select_related
         c._max_related_depth = self._max_related_depth
+        c._fields = self._fields
+        c._modelize = self._modelize
         c._distinct = self._distinct
         c._select = self._select.copy()
         c._where = self._where[:]
@@ -488,7 +511,8 @@
         opts = self.model._meta
 
         # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
-        select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields]
+        select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in \
+                  _get_model_fields(opts, "", self._fields, self._select)]
         tables = [quote_only_if_word(t) for t in self._tables]
         joins = SortedDict()
         where = self._where[:]
@@ -505,7 +529,7 @@
             fill_table_cache(opts, select, tables, where,
                              old_prefix=opts.db_table,
                              cache_tables_seen=[opts.db_table],
-                             max_depth=self._max_related_depth)
+                             max_depth=self._max_related_depth, fields=self._fields)
 
         # Add any additional SELECTs.
         if self._select:
@@ -571,69 +595,6 @@
 else:
     QuerySet = _QuerySet
 
-class ValuesQuerySet(QuerySet):
-    def __init__(self, *args, **kwargs):
-        super(ValuesQuerySet, self).__init__(*args, **kwargs)
-        # select_related isn't supported in values().
-        self._select_related = False
-
-    def iterator(self):
-        try:
-            select, sql, params = self._get_sql_clause()
-        except EmptyResultSet:
-            raise StopIteration
-
-        qn = connection.ops.quote_name
-
-        # self._select is a dictionary, and dictionaries' key order is
-        # undefined, so we convert it to a list of tuples.
-        extra_select = self._select.items()
-
-        # Construct two objects -- fields and field_names.
-        # fields is a list of Field objects to fetch.
-        # field_names is a list of field names, which will be the keys in the
-        # resulting dictionaries.
-        if self._fields:
-            if not extra_select:
-                fields = [self.model._meta.get_field(f, many_to_many=False) for f in self._fields]
-                field_names = self._fields
-            else:
-                fields = []
-                field_names = []
-                for f in self._fields:
-                    if f in [field.name for field in self.model._meta.fields]:
-                        fields.append(self.model._meta.get_field(f, many_to_many=False))
-                        field_names.append(f)
-                    elif not self._select.has_key(f):
-                        raise FieldDoesNotExist('%s has no field named %r' % (self.model._meta.object_name, f))
-        else: # Default to all fields.
-            fields = self.model._meta.fields
-            field_names = [f.attname for f in fields]
-
-        columns = [f.column for f in fields]
-        select = ['%s.%s' % (qn(self.model._meta.db_table), qn(c)) for c in columns]
-        if extra_select:
-            select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in extra_select])
-            field_names.extend([f[0] for f in extra_select])
-
-        cursor = connection.cursor()
-        cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params)
-
-        has_resolve_columns = hasattr(self, 'resolve_columns')
-        while 1:
-            rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
-            if not rows:
-                raise StopIteration
-            for row in rows:
-                if has_resolve_columns:
-                    row = self.resolve_columns(row, fields)
-                yield dict(zip(field_names, row))
-
-    def _clone(self, klass=None, **kwargs):
-        c = super(ValuesQuerySet, self)._clone(klass, **kwargs)
-        c._fields = self._fields[:]
-        return c
-
 class DateQuerySet(QuerySet):
     def iterator(self):
         from django.db.backends.util import typecast_timestamp
@@ -829,36 +790,44 @@
             raise NotImplementedError
     raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
 
-def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0):
+def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0, depth_path="", fields={}, modelize=True):
     """Helper function that recursively returns an object with cache filled"""
 
     # If we've got a max_depth set and we've exceeded that depth, bail now.
     if max_depth and cur_depth > max_depth:
         return None
 
-    index_end = index_start + len(klass._meta.fields)
-    obj = klass(*row[index_start:index_end])
-    for f in klass._meta.fields:
-        if f.rel and not f.null:
-            cached_row = get_cached_row(f.rel.to, row, index_end, max_depth, cur_depth+1)
+    cur_fields = _get_model_fields(klass._meta, depth_path, fields)
+    if not cur_fields:
+        return None
+
+    index_end = index_start + len(cur_fields)
+    obj = _init_model(modelize and klass or None, cur_fields, row[index_start:index_end])
+    for f in cur_fields:
+        if f.rel and not f.null and _get_fields(f, depth_path, fields):
+            cached_row = get_cached_row(f.rel.to, row, index_end, max_depth, cur_depth+1,\
+                                        _lookup(depth_path, f.name), fields, modelize)
             if cached_row:
                 rel_obj, index_end = cached_row
-                setattr(obj, f.get_cache_name(), rel_obj)
+                if modelize:
+                    setattr(obj, f.get_cache_name(), rel_obj)
+                else:
+                    obj[f.name] = rel_obj
     return obj, index_end
 
-def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen, max_depth=0, cur_depth=0):
+def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen,\
+                     max_depth=0, cur_depth=0, depth_path="", fields={}):
     """
     Helper function that recursively populates the select, tables and where (in
     place) for select_related queries.
     """
-
     # If we've got a max_depth set and we've exceeded that depth, bail now.
     if max_depth and cur_depth > max_depth:
         return None
 
     qn = connection.ops.quote_name
-    for f in opts.fields:
-        if f.rel and not f.null:
+    for f in _get_model_fields(opts, depth_path, fields):
+        if f.rel and not f.null and _get_fields(f, depth_path, fields):
             db_table = f.rel.to._meta.db_table
             if db_table not in cache_tables_seen:
                 tables.append(qn(db_table))
@@ -869,9 +838,82 @@
             cache_tables_seen.append(db_table)
             where.append('%s.%s = %s.%s' % \
                 (qn(old_prefix), qn(f.column), qn(db_table), qn(f.rel.get_related_field().column)))
-            select.extend(['%s.%s' % (qn(db_table), qn(f2.column)) for f2 in f.rel.to._meta.fields])
-            fill_table_cache(f.rel.to._meta, select, tables, where, db_table, cache_tables_seen, max_depth, cur_depth+1)
+            select.extend(['%s.%s' % (qn(db_table), qn(f2.column)) for f2 in\
+                           _get_fields(f, depth_path, fields)])
+            fill_table_cache(f.rel.to._meta, select, tables, where, db_table, cache_tables_seen,\
+                             max_depth, cur_depth+1, _lookup(depth_path, f.name), fields)
 
+def _lookup(depth_path, fname):
+    return depth_path and "%s__%s" % (depth_path, fname) or fname
+
+def _split_lookup(lookup):
+    """Right split lookup on parent and child."""
+    if LOOKUP_SEPARATOR in lookup:
+        return lookup.rsplit(LOOKUP_SEPARATOR, 1)
+    else:
+        return '', lookup
+
+def _merge_fields(klass, fields, related_fields):
+    """Resolves child-parent relations and checks is select_related required
+    or not."""
+    _select_related = False
+    if related_fields or [f for f in klass._meta.fields if f.name in fields and f.rel]:
+        _select_related = True
+
+    related_fields[''] = fields
+    # check that each child primary key is in parent field subset
+    for lookup in related_fields.iterkeys():
+        if lookup:                  # root lookup is empty string
+            parent, child = _split_lookup(lookup)
+            if not related_fields.has_key(parent):
+                continue            # a grandparent may have required field
+            parent_fields = related_fields[parent]
+            if child not in parent_fields:
+                parent_fields = list(parent_fields) + [child]
+                related_fields[parent] = parent_fields
+
+    return related_fields, _select_related
+
+def _get_fields(field, depth_path, fields):
+    """Returns fields of field.rel model that need to be selected from db."""
+    assert field.rel and field.rel.to, "ForeignKey is required."
+    return _get_model_fields(field.rel.to._meta, _lookup(depth_path, field.name), fields)
+
+def _get_model_fields(meta, lookup, fields, extra_select=[]):
+    if fields:                    # was used fields(...), so return only specified fields
+        if fields.has_key(lookup):      # we have implicit instruction for the lookup
+            fnames = fields[lookup]
+            if fnames:
+                # check that each field name is valid field name for this model and extra select
+                # actually this is optional step, but tests/modeltests/lookup.py require it
+                valid_fnames = [f.name for f in meta.fields]
+                if extra_select:
+                    valid_fnames += extra_select.keys()
+                for fname in fnames:
+                    if not fname in valid_fnames:
+                        raise FieldDoesNotExist("%s has no field named '%s'" %\
+                                                (meta.object_name, fname))
+            return [f for f in meta.fields if f.name in fnames]
+        else:                           # checking parent
+            while lookup:
+                lookup, child = _split_lookup(lookup)
+                if fields.has_key(lookup):
+                    # we have child defined in parent fields instead of
+                    # dedicated lookup for this child, so return all fields
+                    # for example BlogPost.objects.fields(('headline', 'blog')) should
+                    # return only BlogPost.headline, all Blog fields and all Blog children
+                    # fields (limited to _max_related_depth of course)
+                    return child in fields[lookup] and meta.fields or ()
+    else:
+        return meta.fields
+
+def _init_model(klass, fields, values):
+    """If klass, return model instance, else return dictionary."""
+    obj = dict(zip([f.attname for f in fields], values))
+    if klass:
+        obj = klass(**obj)
+    return obj
+
 def parse_lookup(kwarg_items, opts):
     # Helper function that handles converting API kwargs
     # (e.g. "name__exact": "tom") to SQL.
Index: tests/modeltests/fields/__init__.py
===================================================================

Property changes on: tests/modeltests/fields/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Author Date Id
Name: svn:eol-style
   + native

Index: tests/modeltests/fields/models.py
===================================================================
--- tests/modeltests/fields/models.py	(revision 0)
+++ tests/modeltests/fields/models.py	(revision 0)
@@ -0,0 +1,204 @@
+"""
+47. Tests for fields()
+
+``fields()`` allows to select only fields subset from a model and its related
+models.  It change the behaviour of methods that return model
+instances. Affects querysets with or without select_related mode. If fields()
+isn't called for queryset, queryset behavior isn't chaged, i.e. all fields of
+all selected models are returned.
+"""
+
+from django.db import models
+
+class Author(models.Model):
+    name = models.CharField(max_length=50)
+    about = models.TextField()
+    def __unicode__(self):
+        """When self.about isn't loaded it will be shown as []."""
+        return u"%s [%s]" % (self.name, self.about)
+
+class Blog(models.Model):
+    name = models.CharField(max_length=50)
+    about = models.TextField()
+    author = models.ForeignKey(Author)
+
+    def __unicode__(self):
+        return u"%s [%s]" % (self.name, self.about)
+
+class Entry(models.Model):
+    blog = models.ForeignKey(Blog)
+    headline = models.CharField(max_length=50)
+    content = models.TextField()
+
+    def __unicode__(self):
+        return u"%s [%s]" % (self.headline, self.content)
+
+def create_world():
+    """Helper to create test models."""
+    etch = Author.objects.create(name="Etch", about="About Etch")
+    lenny = Author.objects.create(name="Lenny", about="About Lenny")
+    etch_blog = Blog.objects.create(name="Etch's blog", about="All about Etch", author=etch)
+    lenny_blog = Blog.objects.create(name="Lenny's blog", about="All about Lenny", author=lenny)
+    for blog in Blog.objects.select_related():
+        for i in xrange(5):
+            headline = "%s_%d" % (blog.author.name, i)
+            Entry.objects.create(headline=headline, content="huge content", blog=blog)
+
+
+__test__ = {'API_TESTS':"""
+
+>>> from django.conf import settings
+>>> old_debug = settings.DEBUG
+>>> settings.DEBUG = True
+>>> from django import db
+>>> create_world()
+>>> Author.objects.count(), Blog.objects.count(), Entry.objects.count()
+(2, 2, 10)
+
+# load only `id` and `headline` of a Entry
+>>> db.reset_queries()
+>>> p = Entry.objects.fields('id', 'headline').get(headline='Lenny_0')
+>>> p, p.id, p.content
+(<Entry: Lenny_0 []>, 6, '')
+>>> len(db.connection.queries)
+1
+
+# load all fields of all related objects
+>>> db.reset_queries()
+>>> p = Entry.objects.select_related().get(headline="Etch_1")
+>>> p, p.blog, p.blog.author
+(<Entry: Etch_1 [huge content]>, <Blog: Etch's blog [All about Etch]>, <Author: Etch [About Etch]>)
+>>> len(db.connection.queries)
+1
+
+# load only Entry.headline, Blog.name and Author.name and don't load
+huge TextFields for Lenny's blog posts (typical task when it's need to show an object list).
+>>> db.reset_queries()
+>>> posts = Entry.objects.fields('headline', blog=('name',), blog__author=('name',)).filter(blog__author__name="Lenny")[:1]
+>>> [(p, p.blog, p.blog.author) for p in posts]
+[(<Entry: Lenny_0 []>, <Blog: Lenny's blog []>, <Author: Lenny []>)]
+>>> p = posts[0]
+>>> p.content == p.blog.about == p.blog.author.about == ''
+True
+>>> len(db.connection.queries)
+1
+
+# load all Entry fields and only Blog.headline and Author.name (don't load `about` fields)
+>>> db.reset_queries()
+>>> p = Entry.objects.fields('id','headline', 'content', blog=('name',), blog__author=('name',)).select_related().get(headline="Etch_1")
+>>> p, p.blog, p.blog.author
+(<Entry: Etch_1 [huge content]>, <Blog: Etch's blog []>, <Author: Etch []>)
+>>> print p.content
+huge content
+>>> (p.blog.about, p.blog.author.about)
+('', '')
+>>> len(db.connection.queries)
+1
+
+# don't load any field from Blog, but load Blog.author.name
+>>> db.reset_queries()
+>>> p = Entry.objects.fields('id','headline', 'content', blog=(), blog__author=('name',)).get(headline="Lenny_1")
+>>> p, p.blog, p.blog.author
+(<Entry: Lenny_1 [huge content]>, <Blog:  []>, <Author: Lenny []>)
+>>> len(db.connection.queries)
+1
+
+# select Entry.headline, all fields from Blog and only Author.name
+>>> db.reset_queries()
+>>> p = Entry.objects.fields('headline', 'blog', blog__author=('name',)).get(headline="Lenny_2")
+>>> p, p.blog, p.blog.author
+(<Entry: Lenny_2 []>, <Blog: Lenny's blog [All about Lenny]>, <Author: Lenny []>)
+>>> len(db.connection.queries)
+1
+
+# check count()
+>>> db.reset_queries()
+>>> Entry.objects.fields('id', 'blog', blog__author=('name',)).select_related().filter(headline__contains='1').count()
+2
+>>> len(db.connection.queries)
+1
+
+# select all field from Entry, Blog and don't select Author fields because
+# select_related(depth=1) don't touch Author table and so field limitations from
+# fields() aren't used
+>>> db.reset_queries()
+>>> p = Entry.objects.fields('id','headline', 'content', 'blog', blog__author=('name',)).select_related(depth=1).get(headline="Lenny_3")
+>>> p, p.blog, p.blog.author
+(<Entry: Lenny_3 [huge content]>, <Blog: Lenny's blog [All about Lenny]>, <Author: Lenny [About Lenny]>)
+>>> len(db.connection.queries)
+2
+
+# select Blog.name and Author.name only and check extra()
+>>> db.reset_queries()
+>>> b = Blog.objects.fields('name', author=('name',)).extra(select={'post_count': "SELECT COUNT(*) FROM fields_entry t WHERE t.blog_id=fields_blog.id"}).get(name="Lenny\'s blog")
+>>> b, b.author, b.post_count
+(<Blog: Lenny's blog []>, <Author: Lenny []>, 5)
+>>> len(db.connection.queries)
+1
+
+# check extra() with collection
+>>> db.reset_queries()
+>>> blogs = Blog.objects.fields('id','name', author=('name',)).select_related().extra(select={'post_count': "SELECT COUNT(*) FROM fields_entry t WHERE t.blog_id=fields_blog.id"})
+>>> [(b, b.author, b.post_count) for b in blogs]
+[(<Blog: Etch's blog []>, <Author: Etch []>, 5), (<Blog: Lenny's blog []>, <Author: Lenny []>, 5)]
+>>> len(db.connection.queries)
+1
+
+# load Blog.name, Author.name and check extra()
+>>> db.reset_queries()
+>>> b = Blog.objects.fields('name', author=('name',)).select_related().extra(select={'post_count': "SELECT COUNT(*) FROM fields_entry t WHERE t.blog_id=fields_blog.id"}).get(name="Lenny\'s blog")
+>>> b, b.author, b.post_count
+(<Blog: Lenny's blog []>, <Author: Lenny []>, 5)
+>>> len(db.connection.queries)
+1
+
+# load only Author.name without any fields from Blog
+>>> db.reset_queries()
+>>> b1 = Blog.objects.fields(author=('name',)).select_related().get(name="Etch\'s blog")
+>>> b1, b1.author
+(<Blog:  []>, <Author: Etch []>)
+>>> len(db.connection.queries)
+1
+
+# dive even deeper in absolute beauty, load only child fields without parent fields
+>>> db.reset_queries()
+>>> p3 = Entry.objects.fields(blog=(), blog__author=('id','name',)).select_related().get(headline="Etch_3")
+>>> p3, p3.blog, p3.blog.author, p3.blog.author.id
+(<Entry:  []>, <Blog:  []>, <Author: Etch []>, 1)
+>>> len(db.connection.queries)
+1
+
+# check old-style values()
+>>> d = Entry.objects.filter(blog__author__name='Lenny').values('id', 'headline')[0]
+>>> print d['id'], d['headline']
+6 Lenny_0
+>>> l = Entry.objects.values('id', 'headline').extra(select={'id_plus_1': 'id + 1'})[:2]
+>>> [(d['id'], d['id_plus_1'], str(d['headline'])) for d in l]
+[(1, 2, 'Etch_0'), (2, 3, 'Etch_1')]
+
+# test models for docs/db-api.txt examples
+>>> joe = Author.objects.create(name="Joe")
+>>> blog = Blog.objects.create(id=1, name='Beatles Blog', about='All the latest Beatles news.', author=joe)
+>>> entry = Entry.objects.create(headline='Joe about Beatles', content='Beatles was ...', blog=blog)
+
+# load all fields from Entry and related models
+>>> e = Entry.objects.select_related().get(headline='Joe about Beatles')
+>>> e.headline, e.content, e.blog.name
+(u'Joe about Beatles', u'Beatles was ...', u'Beatles Blog')
+
+# don't load huge text field from Entry and only load name from related Blog
+>>> e = Entry.objects.fields('headline', blog=('name',)).get(headline='Joe about Beatles')
+>>> e.headline, e.content, e.blog.name
+(u'Joe about Beatles', '', u'Beatles Blog')
+
+>>> db.reset_queries()
+>>> Entry.objects.values('headline', blog=('name',)).filter(headline__contains='Beatles')
+[{'headline': u'Joe about Beatles', 'blog': {'name': u'Beatles Blog'}, 'blog_id': 1}]
+>>> len(db.connection.queries)
+1
+>>> Entry.objects.values('headline', blog=('id', 'name',)).filter(headline__contains='Beatles')
+[{'headline': u'Joe about Beatles', 'blog': {'id': 1, 'name': u'Beatles Blog'}, 'blog_id': 1}]
+
+# Reset DEBUG to where we found it.
+>>> settings.DEBUG = old_debug
+"""}

Property changes on: tests/modeltests/fields/models.py
___________________________________________________________________
Name: svn:keywords
   + Author Date Id
Name: svn:eol-style
   + native

Index: AUTHORS
===================================================================
--- AUTHORS	(revision 7111)
+++ AUTHORS	(working copy)
@@ -357,6 +357,7 @@
     ymasuda@ethercube.com
     Jarek Zgoda <jarek.zgoda@gmail.com>
     Cheng Zhang
+    Dima Dogadaylo <http://www.mysoftparade.com/>
 
 A big THANK YOU goes to:
 
Index: docs/db-api.txt
===================================================================
--- docs/db-api.txt	(revision 7111)
+++ docs/db-api.txt	(working copy)
@@ -536,12 +536,64 @@
 However, if your query spans multiple tables, it's possible to get duplicate
 results when a ``QuerySet`` is evaluated. That's when you'd use ``distinct()``.
 
-``values(*fields)``
+``fields(*fields, **related_fields)``
 ~~~~~~~~~~~~~~~~~~~
 
-Returns a ``ValuesQuerySet`` -- a ``QuerySet`` that evaluates to a list of
-dictionaries instead of model-instance objects.
+**New in Django development version**
 
+Returns a ``QuerySet`` that allows to load only some of fields from this and
+related models.
+
+This example demonstrates effect of particular field loading::
+
+    # load all fields from Entry and related models
+    >>> e = Entry.objects.select_related().get(headline='Joe about Beatles')
+    >>> e.headline, e.body_text, e.blog.name
+    (u'Joe about Beatles', u'Beatles was ...', u'Beatles Blog')
+
+    # don't load huge text field from Entry and only load name from related Blog
+    >>> e = Entry.objects.fields('headline', blog=('name',)).get(headline='Joe about Beatles')
+    >>> e.headline, e.body_text, e.blog.name
+    (u'Joe about Beatles', '', u'Beatles Blog')
+
+``fields()`` takes optional positional arguments, ``*fields``, which specify
+field names of this model for to which the ``SELECT`` should be limited. 
+
+``fields()`` also takes optional keyword arguments, ``**related_fields``,
+which specify field names of related models to which the ``SELECT`` should be
+limited. Each argument in ``**related_fields`` affects corresponding related
+model in the same way like ``*fields`` affects master model.
+
+You can reference related objects for any desired depth with standard Django
+lookup syntax, for example:
+
+    Entry.objects.fields('headline', author__address__country=('name'))
+
+The method ``fields()`` is extremelly usefull when you have models that
+contain large text or binary fields and you don't want to load these heavy
+fields. For example you need to show latest 10 blog posts and you only need
+headlines of these blog posts and their author names, with ``fields`` you can
+load only ``headline`` from ``Entry`` table and only ``name`` from ``Author``
+table (other fields of these 2 models will not be loaded and will have default
+values). 
+
+With ``fields()`` you have full control on what fields will be loaded from
+database, take this power with care. For example suppose ``Entry`` model
+references ``Author`` model, that references ``Address`` model that have
+foreign key to ``Country`` model. With call like
+    Entry.objects.fields('headline', author__address__country=('name'))
+, you can load ``Entry.headline``, don't load fields from ``Author`` and
+``Address`` and load only name from ``Country`` assotiated with blog entry's
+author.
+
+``values(*fields, **related_fields)``
+~~~~~~~~~~~~~~~~~~~
+
+**New in Django development version**
+
+Returns a ``QuerySet`` that evaluates to a list of dictionaries instead of
+model-instance objects.
+
 Each of those dictionaries represents an object, with the keys corresponding to
 the attribute names of model objects.
 
@@ -569,13 +621,30 @@
     >>> Blog.objects.values('id', 'name')
     [{'id': 1, 'name': 'Beatles Blog'}]
 
-A ``ValuesQuerySet`` is useful when you know you're only going to need values
+``values()`` also takes optional keyword arguments, ``**related_fields``,
+which specify field names of related models to which the ``SELECT`` should be
+limited. Each argument in ``**related_fields`` affects corresponding related
+model in the same way like ``*fields`` affects master model.
+
+Example::
+
+    >>> Entry.objects.values('headline', blog=('id', 'name',)).filter(headline__contains='Beatles')
+    [{'headline': u'Joe about Beatles', 'blog': {'name': u'Beatles Blog'}, 'blog_id': 1}]
+    >>> Entry.objects.values('headline', blog=('id', 'name',)).filter(headline__contains='Beatles')
+    [{'headline': u'Joe about Beatles', 'blog': {'id': 1, 'name': u'Beatles Blog'}, 'blog_id': 1}]
+
+Note, when fields from related models is selected, primary keys of related
+models also populated in parent model dictionary.
+
+It is useful when you know you're only going to need values
 from a small number of the available fields and you won't need the
 functionality of a model instance object. It's more efficient to select only
 the fields you need to use.
 
-Finally, note a ``ValuesQuerySet`` is a subclass of ``QuerySet``, so it has all
-methods of ``QuerySet``. You can call ``filter()`` on it, or ``order_by()``, or
+Finally, note ``values()`` and ``fields()`` use same code for fields selecting
+and only diferent format returned. All that you can do with ``values()`` you
+can do with ``fields()`` and vise versa. And for sure after ``values()`` and
+``fields()`` you can call call ``filter()``, or ``order_by()``, or
 whatever. Yes, that means these two calls are identical::
 
     Blog.objects.values().order_by('id')
