Django

Code

Changeset 7230

Show
Ignore:
Timestamp:
03/12/08 07:41:58 (6 months ago)
Author:
mtredinnick
Message:

queryset-refactor: Refactored the way values() works so that it works properly
across inherited models.

Completely by accident, this also allows values() queries to include fields
from related models, providing it is crossing a single-valued relation
(one-to-one, many-to-one). Many-to-many values() fields still aren't supported,
since that requires actual thinking. So this refs #5768.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/queryset-refactor/django/db/models/query.py

    r7169 r7230  
    479479    def _setup_query(self): 
    480480        """ 
    481         Sets up any special features of the query attribute. 
     481        Constructs the field_names list that the values query will be 
     482        retrieving. 
    482483 
    483484        Called by the _clone() method after initialising the rest of the 
    484485        instance. 
    485486        """ 
    486         # Construct two objects: 
    487         #   - fields is a list of Field objects to fetch. 
    488         #   - field_names is a list of field names, which will be the keys in 
    489         #   the resulting dictionaries. 
    490         # 'fields' is used to configure the query, whilst field_names is stored 
    491         # in this object for use by iterator(). 
    492487        if self._fields: 
    493             opts = self.model._meta 
    494             all = dict([(field.column, field) for field in opts.fields]) 
    495             for field in opts.fields: 
    496                 all[field.name] = field 
    497488            if not self.query.extra_select: 
    498                 try: 
    499                     fields = [all[f] for f in self._fields] 
    500                 except KeyError, e: 
    501                     raise FieldDoesNotExist('%s has no field named %r' 
    502                                 % (opts.object_name, e.args[0])) 
    503489                field_names = list(self._fields) 
    504490            else: 
    505                 fields = [] 
    506491                field_names = [] 
     492                names = set(self.model._meta.get_all_field_names()) 
    507493                for f in self._fields: 
    508                     if f in all: 
    509                         fields.append(all[f]) 
     494                    if f in names: 
    510495                        field_names.append(f) 
    511496                    elif not self.query.extra_select.has_key(f): 
    512497                        raise FieldDoesNotExist('%s has no field named %r' 
    513498                                % (self.model._meta.object_name, f)) 
    514         else: # Default to all fields. 
    515             fields = self.model._meta.fields 
    516             field_names = [f.attname for f in fields] 
    517  
    518         self.query.add_local_columns([f.column for f in fields]
     499        else: 
     500            # Default to all fields. 
     501            field_names = [f.attname for f in self.model._meta.fields] 
     502 
     503        self.query.add_fields(field_names
    519504        self.query.default_cols = False 
    520505        self.field_names = field_names 
  • django/branches/queryset-refactor/django/db/models/sql/query.py

    r7224 r7230  
    917917            self.where.end_subtree() 
    918918 
    919     def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True): 
     919    def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True, 
     920            allow_explicit_fk=False): 
    920921        """ 
    921922        Compute the necessary table joins for the passage through the fields 
     
    940941                field, model, direct, m2m = opts.get_field_by_name(name) 
    941942            except FieldDoesNotExist: 
    942                 names = opts.get_all_field_names() 
    943                 raise FieldError("Cannot resolve keyword %r into field. " 
    944                         "Choices are: %s" % (name, ", ".join(names))) 
     943                for f in opts.fields: 
     944                    if allow_explicit_fk and name == f.attname: 
     945                        # XXX: A hack to allow foo_id to work in values() for 
     946                        # backwards compatibility purposes. If we dropped that 
     947                        # feature, this could be removed. 
     948                        field, model, direct, m2m = opts.get_field_by_name(f.name) 
     949                        break 
     950                else: 
     951                    names = opts.get_all_field_names() 
     952                    raise FieldError("Cannot resolve keyword %r into field. " 
     953                            "Choices are: %s" % (name, ", ".join(names))) 
    945954            if not allow_many and (m2m or not direct): 
    946955                for join in joins: 
     
    11031112        return not (self.low_mark or self.high_mark) 
    11041113 
    1105     def add_local_columns(self, columns): 
    1106         """ 
    1107         Adds the given column names to the select set, assuming they come from 
    1108         the root model (the one given in self.model)
    1109         """ 
    1110         for alias in self.tables: 
    1111             if self.alias_map[alias][ALIAS_REFCOUNT]: 
    1112                 break 
    1113         else: 
    1114             alias = self.get_initial_alias(
    1115         self.select.extend([(alias, col) for col in columns]
     1114    def add_fields(self, field_names): 
     1115        """ 
     1116        Adds the given (model) fields to the select set. The field names are 
     1117        added in the order specified
     1118        """ 
     1119        alias = self.get_initial_alias() 
     1120        opts = self.get_meta() 
     1121        for name in field_names: 
     1122            u1, target, u2, joins = self.setup_joins(name.split(LOOKUP_SEP), 
     1123                    opts, alias, False, False, True
     1124            self.select.append((joins[-1][-1], target.column)
    11161125 
    11171126    def add_ordering(self, *ordering): 
  • django/branches/queryset-refactor/django/db/models/sql/subqueries.py

    r7190 r7230  
    161161        alias = '%s0' % self.alias_prefix 
    162162        query.change_alias(query.tables[0], alias) 
    163         self.add_local_columns([query.model._meta.pk.column]) 
     163        self.add_fields([query.model._meta.pk.name]) 
    164164 
    165165        # Now we adjust the current query: reset the where clause and get rid 
  • django/branches/queryset-refactor/docs/db-api.txt

    r7221 r7230  
    596596    >>> Blog.objects.values('id', 'name') 
    597597    [{'id': 1, 'name': 'Beatles Blog'}] 
     598 
     599You can also retrieve values from across ``ForeignKey`` relations by using 
     600double underscores to separate the field names, just as when calling the 
     601``filter()`` command. For example:: 
     602 
     603    >>> Entry.objects.values('blog__name').distinct() 
     604    [{'name': 'Beatles Blog'}] 
    598605 
    599606A couple of subtleties that are worth mentioning: 
  • django/branches/queryset-refactor/tests/modeltests/many_to_one/models.py

    r7163 r7230  
    250250>>> Reporter.objects.filter(article__reporter=r).distinct() 
    251251[<Reporter: John Smith>] 
     252 
     253# It's possible to use values() calls across many-to-one relations. 
     254>>> d = {'reporter__first_name': u'John', 'reporter__last_name': u'Smith'} 
     255>>> list(Article.objects.filter(reporter=r).distinct().values('reporter__first_name', 'reporter__last_name')) == [d] 
     256True 
    252257 
    253258# If you delete a reporter, his articles will be deleted. 
  • django/branches/queryset-refactor/tests/modeltests/model_inheritance/models.py

    r7218 r7230  
    1515from django.db import models 
    1616 
     17# 
     18# Abstract base classes 
     19# 
     20 
    1721class CommonInfo(models.Model): 
    1822    name = models.CharField(max_length=50) 
     
    3438    class Meta: 
    3539        pass 
     40 
     41# 
     42# Multi-table inheritance 
     43# 
    3644 
    3745class Place(models.Model): 
     
    228236u'Demon Puppies' 
    229237 
     238# The values() command also works on fields from parent models. 
     239>>> d = {'rating': 4, 'name': u'Ristorante Miron'} 
     240>>> list(ItalianRestaurant.objects.values('name', 'rating')) == [d] 
     241True 
     242 
    230243"""}