Django

Code

Changeset 6899

Show
Ignore:
Timestamp:
12/09/07 00:24:17 (7 months ago)
Author:
mtredinnick
Message:

queryset-refactor: Allow specifying of specific relations to follow in
select_related(). Refs #5020.

Files:

Legend:

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

    r6857 r6899  
    8686        """ 
    8787        fill_cache = self.query.select_related 
     88        if isinstance(fill_cache, dict): 
     89            requested = fill_cache 
     90        else: 
     91            requested = None 
    8892        max_depth = self.query.max_depth 
    8993        index_end = len(self.model._meta.fields) 
     
    9195        for row in self.query.results_iter(): 
    9296            if fill_cache: 
    93                 obj, index_end = get_cached_row(klass=self.model, row=row
    94                         index_start=0, max_depth=max_depth
     97                obj, index_end = get_cached_row(self.model, row, 0, max_depth
     98                        requested=requested
    9599            else: 
    96100                obj = self.model(*row[:index_end]) 
     
    299303            return self._filter_or_exclude(None, **filter_obj) 
    300304 
    301     def select_related(self, true_or_false=True, depth=0): 
    302         """Returns a new QuerySet instance that will select related objects.""" 
     305    def select_related(self, *fields, **kwargs): 
     306        """ 
     307        Returns a new QuerySet instance that will select related objects. If 
     308        fields are specified, they must be ForeignKey fields and only those 
     309        related objects are included in the selection. 
     310        """ 
     311        depth = kwargs.pop('depth', 0) 
     312        # TODO: Remove this? select_related(False) isn't really useful. 
     313        true_or_false = kwargs.pop('true_or_false', True) 
     314        if kwargs: 
     315            raise TypeError('Unexpected keyword arguments to select_related: %s' 
     316                    % (kwargs.keys(),)) 
    303317        obj = self._clone() 
    304         obj.query.select_related = true_or_false 
     318        if fields: 
     319            if depth: 
     320                raise TypeError('Cannot pass both "depth" and fields to select_related()') 
     321            obj.query.add_select_related(fields) 
     322        else: 
     323            obj.query.select_related = true_or_false 
    305324        if depth: 
    306325            obj.query.max_depth = depth 
     
    371390    def __init__(self, *args, **kwargs): 
    372391        super(ValuesQuerySet, self).__init__(*args, **kwargs) 
    373         # select_related isn't supported in values(). 
     392        # select_related isn't supported in values(). (FIXME -#3358) 
    374393        self.query.select_related = False 
    375394 
     
    491510QOr = QAnd = QOperator 
    492511 
    493 def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0): 
     512def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0, 
     513        requested=None): 
    494514    """Helper function that recursively returns an object with cache filled""" 
    495515 
    496     # If we've got a max_depth set and we've exceeded that depth, bail now. 
    497     if max_depth and cur_depth > max_depth: 
     516    if max_depth and requested is None and cur_depth > max_depth: 
     517        # We've recursed deeply enough; stop now. 
    498518        return None 
    499519 
     520    restricted = requested is not None 
    500521    index_end = index_start + len(klass._meta.fields) 
    501522    obj = klass(*row[index_start:index_end]) 
    502523    for f in klass._meta.fields: 
    503         if f.rel and not f.null: 
    504             cached_row = get_cached_row(f.rel.to, row, index_end, max_depth, cur_depth+1) 
     524        if f.rel and ((not restricted and not f.null) or 
     525                (restricted and f.name in requested)): 
     526            if restricted: 
     527                next = requested[f.name] 
     528            else: 
     529                next = None 
     530            cached_row = get_cached_row(f.rel.to, row, index_end, max_depth, 
     531                    cur_depth+1, next) 
    505532            if cached_row: 
    506533                rel_obj, index_end = cached_row 
  • django/branches/queryset-refactor/django/db/models/sql/query.py

    r6869 r6899  
    637637 
    638638    def fill_related_selections(self, opts=None, root_alias=None, cur_depth=1, 
    639             used=None): 
     639            used=None, requested=None, restricted=None): 
    640640        """ 
    641641        Fill in the information needed for a select_related query. The current 
    642         "depth" is measured as the number of connections away from the root 
    643         model (cur_depth == 1 means we are looking at models with direct 
     642        depth is measured as the number of connections away from the root model 
     643        (for example, cur_depth=1 means we are looking at models with direct 
    644644        connections to the root model). 
    645645        """ 
    646         if self.max_depth and cur_depth > self.max_depth: 
    647             # We've recursed too deeply; bail out. 
     646        if not restricted and self.max_depth and cur_depth > self.max_depth: 
     647            # We've recursed far enough; bail out. 
    648648            return 
    649649        if not opts: 
     
    654654            used = [] 
    655655 
     656        # Setup for the case when only particular related fields should be 
     657        # included in the related selection. 
     658        if requested is None and restricted is not False: 
     659            if isinstance(self.select_related, dict): 
     660                requested = self.select_related 
     661                restricted = True 
     662            else: 
     663                restricted = False 
     664 
    656665        for f in opts.fields: 
    657             if not f.rel or f.null: 
     666            if (not f.rel or (restricted and f.name not in requested) or 
     667                    (not restricted and f.null)): 
    658668                continue 
    659669            table = f.rel.to._meta.db_table 
     
    663673            self.select.extend([(alias, f2.column) 
    664674                    for f2 in f.rel.to._meta.fields]) 
     675            if restricted: 
     676                next = requested.get(f.name, {}) 
     677            else: 
     678                next = False 
    665679            self.fill_related_selections(f.rel.to._meta, alias, cur_depth + 1, 
    666                     used
     680                    used, next, restricted
    667681 
    668682    def add_filter(self, filter_expr, connector=AND, negate=False): 
     
    10071021        self.extra_select = SortedDict() 
    10081022 
     1023    def add_select_related(self, fields): 
     1024        """ 
     1025        Sets up the select_related data structure so that we only select 
     1026        certain related models (as opposed to all models, when 
     1027        self.select_related=True). 
     1028        """ 
     1029        field_dict = {} 
     1030        for field in fields: 
     1031            d = field_dict 
     1032            for part in field.split(LOOKUP_SEP): 
     1033                d = d.setdefault(part, {}) 
     1034        self.select_related = field_dict 
     1035 
    10091036    def execute_sql(self, result_type=MULTI): 
    10101037        """ 
  • django/branches/queryset-refactor/docs/db-api.txt

    r6760 r6899  
    745745    c = p.hometown       # Hits the database. 
    746746 
    747 Note that ``select_related()`` does not follow foreign keys that have 
    748 ``null=True``. 
     747Note that, by default, ``select_related()`` does not follow foreign keys that 
     748have ``null=True``. 
    749749 
    750750Usually, using ``select_related()`` can vastly improve performance because your 
     
    762762 
    763763The ``depth`` argument is new in the Django development version. 
     764 
     765**New in Django development version:** Sometimes you only need to access 
     766specific models that are related to your root model, not all of the related 
     767models. In these cases, you can pass the related field names to 
     768``select_related()`` and it will only follow those relations. You can even do 
     769this for models that are more than one relation away by separating the field 
     770names with double underscores, just as for filters. For example, if we have 
     771thise model:: 
     772 
     773    class Room(models.Model): 
     774        # ... 
     775        building = models.ForeignKey(...) 
     776 
     777    class Group(models.Model): 
     778        # ... 
     779        teacher = models.ForeignKey(...) 
     780        room = models.ForeignKey(Room) 
     781        subject = models.ForeignKey(...) 
     782 
     783...and we only needed to work with the ``room`` and ``subject`` attributes, we 
     784could write this:: 
     785 
     786    g = Group.objects.select_related('room', 'subject') 
     787 
     788This is also valid:: 
     789 
     790    g = Group.objects.select_related('room__building', 'subject') 
     791 
     792...and would also pull in the ``building`` relation. 
     793 
     794You can only refer to ``ForeignKey`` relations in the list of fields passed to 
     795``select_related``. You *can* refer to foreign keys that have ``null=True`` 
     796(unlike the default ``select_related()`` call). It's an error to use both a 
     797list of fields and the ``depth`` parameter in the same ``select_related()`` 
     798call, since they are conflicting options. 
    764799 
    765800``extra(select=None, where=None, params=None, tables=None, order_by=None)`` 
  • django/branches/queryset-refactor/tests/modeltests/select_related/models.py

    r6857 r6899  
    130130<Domain: Eukaryota> 
    131131 
    132 # Notice: one few query than above because of depth=1 
     132# Notice: one fewer queries than above because of depth=1 
    133133>>> len(db.connection.queries) 
    1341347 
     
    152152True 
    153153 
    154 # Reset DEBUG to where we found it. 
     154# The optional fields passed to select_related() control which related models 
     155# we pull in. This allows for smaller queries and can act as an alternative 
     156# (or, in addition to) the depth parameter. 
     157 
     158# In the next two cases, we explicitly say to select the 'genus' and 
     159# 'genus.family' models, leading to the same number of queries as before. 
     160>>> db.reset_queries() 
     161>>> world = Species.objects.select_related('genus__family') 
     162>>> [o.genus.family for o in world] 
     163[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>] 
     164>>> len(db.connection.queries) 
     165
     166 
     167>>> db.reset_queries() 
     168>>> world = Species.objects.filter(genus__name='Amanita').select_related('genus__family') 
     169>>> [o.genus.family.order for o in world] 
     170[<Order: Agaricales>] 
     171>>> len(db.connection.queries) 
     172
     173 
     174>>> db.reset_queries() 
     175>>> Species.objects.all().select_related('genus__family__order').order_by('id')[0:1].get().genus.family.order.name 
     176u'Diptera' 
     177>>> len(db.connection.queries) 
     178
     179 
     180# Specifying both "depth" and fields is an error. 
     181>>> Species.objects.select_related('genus__family__order', depth=4) 
     182Traceback (most recent call last): 
     183... 
     184TypeError: Cannot pass both "depth" and fields to select_related() 
     185 
     186# Reser DEBUG to where we found it. 
    155187>>> settings.DEBUG = False 
    156188"""}