Django

Code

Changeset 7126

Show
Ignore:
Timestamp:
02/17/08 12:47:57 (3 months ago)
Author:
mtredinnick
Message:

queryset-refactor: Model inheritance support.

This adds both types of model inheritance: abstract base classes (ABCs) and
multi-table inheritance. See the documentation and tests / examples for details.

Still a few known bugs here, so don't file tickets (I know about them). Not
quite ready for prime-time usage, but it mostly works as expected.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/queryset-refactor/django/core/management/sql.py

    r7004 r7126  
    2727        for model in models.get_models(app): 
    2828            tables.append(model._meta.db_table) 
    29             tables.extend([f.m2m_db_table() for f in model._meta.many_to_many]) 
     29            tables.extend([f.m2m_db_table() for f in model._meta.local_many_to_many]) 
    3030    if only_existing: 
    3131        existing = table_list() 
     
    5555    for app in apps: 
    5656        for model in models.get_models(app): 
    57             for f in model._meta.fields: 
     57            for f in model._meta.local_fields: 
    5858                if isinstance(f, models.AutoField): 
    5959                    sequence_list.append({'table': model._meta.db_table, 'column': f.column}) 
    6060                    break # Only one AutoField is allowed per model, so don't bother continuing. 
    6161 
    62             for f in model._meta.many_to_many: 
     62            for f in model._meta.local_many_to_many: 
    6363                sequence_list.append({'table': f.m2m_db_table(), 'column': None}) 
    6464 
     
    148148            # The table exists, so it needs to be dropped 
    149149            opts = model._meta 
    150             for f in opts.fields: 
     150            for f in opts.local_fields: 
    151151                if f.rel and f.rel.to not in to_delete: 
    152152                    references_to_delete.setdefault(f.rel.to, []).append( (model, f) ) 
     
    180180    for model in app_models: 
    181181        opts = model._meta 
    182         for f in opts.many_to_many: 
     182        for f in opts.local_many_to_many: 
    183183            if isinstance(f.rel, generic.GenericRel): 
    184184                continue 
     
    257257    qn = connection.ops.quote_name 
    258258    inline_references = connection.features.inline_fk_references 
    259     for f in opts.fields: 
     259    for f in opts.local_fields: 
    260260        col_type = f.db_type() 
    261261        tablespace = f.db_tablespace or opts.db_tablespace 
     
    352352    qn = connection.ops.quote_name 
    353353    inline_references = connection.features.inline_fk_references 
    354     for f in opts.many_to_many: 
     354    for f in opts.local_many_to_many: 
    355355        if not isinstance(f.rel, generic.GenericRel): 
    356356            tablespace = f.db_tablespace or opts.db_tablespace 
     
    459459 
    460460    qn = connection.ops.quote_name 
    461     for f in model._meta.fields: 
     461    for f in model._meta.local_fields: 
    462462        if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys): 
    463463            unique = f.unique and 'UNIQUE ' or '' 
  • django/branches/queryset-refactor/django/core/management/validation.py

    r6857 r7126  
    3333 
    3434        # Do field-specific validation. 
    35         for f in opts.fields: 
     35        for f in opts.local_fields: 
    3636            if f.name == 'id' and not f.primary_key and opts.pk.name == 'id': 
    3737                e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name) 
     
    7070                    e.add(opts, '"%s": %s cannot have a "max_length" greater than 255 when you are using a version of MySQL prior to 5.0.3 (you are using %s).' % (f.name, f.__class__.__name__, '.'.join([str(n) for n in db_version[:3]]))) 
    7171 
    72             # Check to see if the related field will clash with any 
    73             # existing fields, m2m fields, m2m related objects or related objects 
     72            # Check to see if the related field will clash with any existing 
     73            # fields, m2m fields, m2m related objects or related objects 
    7474            if f.rel: 
    7575                if f.rel.to not in models.get_models(): 
     
    8888                    if r.name == rel_query_name: 
    8989                        e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) 
    90                 for r in rel_opts.many_to_many: 
     90                for r in rel_opts.local_many_to_many: 
    9191                    if r.name == rel_name: 
    9292                        e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) 
     
    105105                            e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) 
    106106 
    107         for i, f in enumerate(opts.many_to_many): 
     107        for i, f in enumerate(opts.local_many_to_many): 
    108108            # Check to see if the related m2m field will clash with any 
    109             # existing fields, m2m fields, m2m related objects or related objects 
     109            # existing fields, m2m fields, m2m related objects or related 
     110            # objects 
    110111            if f.rel.to not in models.get_models(): 
    111112                e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, f.rel.to)) 
     
    118119            rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() 
    119120            rel_query_name = f.related_query_name() 
    120             # If rel_name is none, there is no reverse accessor. 
    121             # (This only occurs for symmetrical m2m relations to self). 
    122             # If this is the case, there are no clashes to check for this field, as 
    123             # there are no reverse descriptors for this field. 
     121            # If rel_name is none, there is no reverse accessor (this only 
     122            # occurs for symmetrical m2m relations to self). If this is the 
     123            # case, there are no clashes to check for this field, as there are 
     124            # no reverse descriptors for this field. 
    124125            if rel_name is not None: 
    125126                for r in rel_opts.fields: 
     
    128129                    if r.name == rel_query_name: 
    129130                        e.add(opts, "Reverse query name for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) 
    130                 for r in rel_opts.many_to_many: 
     131                for r in rel_opts.local_many_to_many: 
    131132                    if r.name == rel_name: 
    132133                        e.add(opts, "Accessor for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) 
  • django/branches/queryset-refactor/django/db/models/base.py

    r7124 r7126  
     1import types 
     2import sys 
     3import os 
     4from itertools import izip 
     5 
    16import django.db.models.manipulators 
    27import django.db.models.manager 
     
    49from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned 
    510from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist 
    6 from django.db.models.fields.related import OneToOneRel, ManyToOneRel 
     11from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField 
    712from django.db.models.query import delete_objects, Q 
    813from django.db.models.options import Options, AdminOptions 
     
    1520from django.utils.encoding import smart_str, force_unicode, smart_unicode 
    1621from django.conf import settings 
    17 from itertools import izip 
    18 import types 
    19 import sys 
    20 import os 
    2122 
    2223class ModelBase(type): 
     
    2627        try: 
    2728            parents = [b for b in bases if issubclass(b, Model)] 
    28             if not parents: 
    29                 return super(ModelBase, cls).__new__(cls, name, bases, attrs) 
    3029        except NameError: 
    3130            # 'Model' isn't defined yet, meaning we're looking at Django's own 
    3231            # Model class, defined below. 
     32            parents = [] 
     33        if not parents: 
    3334            return super(ModelBase, cls).__new__(cls, name, bases, attrs) 
    3435 
    3536        # Create the class. 
    3637        new_class = type.__new__(cls, name, bases, {'__module__': attrs.pop('__module__')}) 
    37         new_class.add_to_class('_meta', Options(attrs.pop('Meta', None))) 
     38        meta = attrs.pop('Meta', None) 
     39        # FIXME: Promote Meta to a newstyle class before attaching it to the 
     40        # model. 
     41        ## if meta: 
     42        ##     new_class.Meta = meta 
     43        new_class.add_to_class('_meta', Options(meta)) 
     44        # FIXME: Need to be smarter here. Exception is an old-style class in 
     45        # Python <= 2.4, new-style in Python 2.5+. This construction is only 
     46        # really correct for old-style classes. 
    3847        new_class.add_to_class('DoesNotExist', types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {})) 
    39         new_class.add_to_class('MultipleObjectsReturned', 
    40             types.ClassType('MultipleObjectsReturned', (MultipleObjectsReturned, ), {})) 
    41  
    42         # Build complete list of parents 
     48        new_class.add_to_class('MultipleObjectsReturned', types.ClassType('MultipleObjectsReturned', (MultipleObjectsReturned, ), {})) 
     49 
     50        # Do the appropriate setup for any model parents. 
     51        abstract_parents = [] 
    4352        for base in parents: 
    44             # Things without _meta aren't functional models, so they're 
    45             # uninteresting parents. 
    46             if hasattr(base, '_meta'): 
    47                 new_class._meta.parents.append(base) 
    48                 new_class._meta.parents.extend(base._meta.parents) 
    49  
    50  
     53            if not hasattr(base, '_meta'): 
     54                # Things without _meta aren't functional models, so they're 
     55                # uninteresting parents. 
     56                continue 
     57            if not base._meta.abstract: 
     58                attr_name = '%s_ptr' % base._meta.module_name 
     59                field = OneToOneField(base, name=attr_name, auto_created=True) 
     60                new_class.add_to_class(attr_name, field) 
     61                new_class._meta.parents[base] = field 
     62            else: 
     63                abstract_parents.append(base) 
     64 
     65        if getattr(new_class, '_default_manager', None) is not None: 
     66            # We have a parent who set the default manager. We need to override 
     67            # this. 
     68            new_class._default_manager = None 
    5169        if getattr(new_class._meta, 'app_label', None) is None: 
    5270            # Figure out the app_label by looking one level up. 
     
    6482            new_class.add_to_class(obj_name, obj) 
    6583 
    66         # Add Fields inherited from parents 
    67         for parent in new_class._meta.parents: 
    68             for field in parent._meta.fields: 
    69                 # Only add parent fields if they aren't defined for this class. 
    70                 try: 
    71                     new_class._meta.get_field(field.name) 
    72                 except FieldDoesNotExist: 
    73                     field.contribute_to_class(new_class, field.name) 
     84        for parent in abstract_parents: 
     85            names = [f.name for f in new_class._meta.local_fields + new_class._meta.many_to_many] 
     86            for field in parent._meta.local_fields: 
     87                if field.name in names: 
     88                    raise TypeError('Local field %r in class %r clashes with field of similar name from abstract base class %r' 
     89                            % (field.name, name, parent.__name__)) 
     90                new_class.add_to_class(field.name, field) 
     91 
     92        if new_class._meta.abstract: 
     93            # Abstract base models can't be instantiated and don't appear in 
     94            # the list of models for an app. We do the final setup for them a 
     95            # little differently from normal models. 
     96            return new_class 
    7497 
    7598        new_class._prepare() 
    76  
    7799        register_models(new_class._meta.app_label, new_class) 
     100 
    78101        # Because of the way imports happen (recursively), we may or may not be 
    79         # the first class for this model to register with the framework. There 
    80         # should only be one class for each model, so we must always return the 
     102        # the first time this model tries to register with the framework. There 
     103        # should only be one class for each model, so we always return the 
    81104        # registered version. 
    82105        return get_model(new_class._meta.app_label, name, False) 
     
    114137    __metaclass__ = ModelBase 
    115138 
    116     def _get_pk_val(self): 
    117         return getattr(self, self._meta.pk.attname) 
     139    def _get_pk_val(self, meta=None): 
     140        if not meta: 
     141            meta = self._meta 
     142        return getattr(self, meta.pk.attname) 
    118143 
    119144    def _set_pk_val(self, value): 
     
    208233        dispatcher.send(signal=signals.post_init, sender=self.__class__, instance=self) 
    209234 
    210     def save(self, raw=False): 
    211         dispatcher.send(signal=signals.pre_save, sender=self.__class__, 
    212                         instance=self, raw=raw) 
    213  
    214         non_pks = [f for f in self._meta.fields if not f.primary_key] 
     235    def save(self, raw=False, cls=None): 
     236        if not cls: 
     237            dispatcher.send(signal=signals.pre_save, sender=self.__class__, 
     238                    instance=self, raw=raw) 
     239            cls = self.__class__ 
     240            meta = self._meta 
     241            signal = True 
     242        else: 
     243            meta = cls._meta 
     244            signal = False 
     245 
     246        for parent, field in meta.parents.items(): 
     247            self.save(raw, parent) 
     248            setattr(self, field.attname, self._get_pk_val(parent._meta)) 
     249 
     250        non_pks = [f for f in self._meta.local_fields if not f.primary_key] 
    215251 
    216252        # First, try an UPDATE. If that doesn't update anything, do an INSERT. 
    217         pk_val = self._get_pk_val(
     253        pk_val = self._get_pk_val(meta
    218254        # Note: the comparison with '' is required for compatibility with 
    219255        # oldforms-style model creation. 
    220256        pk_set = pk_val is not None and smart_unicode(pk_val) != u'' 
    221257        record_exists = True 
    222         manager = self.__class__._default_manager 
     258        manager = cls._default_manager 
    223259        if pk_set: 
    224260            # Determine whether a record with the primary key already exists. 
     
    232268        if not pk_set or not record_exists: 
    233269            if not pk_set: 
    234                 values = [(f.name, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in self._meta.fields if not isinstance(f, AutoField)] 
    235             else: 
    236                 values = [(f.name, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in self._meta.fields] 
    237  
    238             if self._meta.order_with_respect_to: 
    239                 field = self._meta.order_with_respect_to 
     270                values = [(f.name, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)] 
     271            else: 
     272                values = [(f.name, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields] 
     273 
     274            if meta.order_with_respect_to: 
     275                field = meta.order_with_respect_to 
    240276                values.append(('_order', manager.filter(**{field.name: getattr(self, field.attname)}).count())) 
    241277            record_exists = False 
    242278 
    243             update_pk = bool(self._meta.has_auto_field and not pk_set) 
     279            update_pk = bool(meta.has_auto_field and not pk_set) 
    244280            if values: 
    245281                # Create a new record. 
     
    251287 
    252288            if update_pk: 
    253                 setattr(self, self._meta.pk.attname, result) 
     289                setattr(self, meta.pk.attname, result) 
    254290        transaction.commit_unless_managed() 
    255291 
    256         # Run any post-save hooks. 
    257         dispatcher.send(signal=signals.post_save, sender=self.__class__, 
    258                         instance=self, created=(not record_exists), raw=raw) 
     292        if signal: 
     293            # Run any post-save hooks. 
     294            dispatcher.send(signal=signals.post_save, sender=self.__class__, 
     295                    instance=self, created=(not record_exists), raw=raw) 
    259296 
    260297    save.alters_data = True 
  • django/branches/queryset-refactor/django/db/models/fields/__init__.py

    r7029 r7126  
    7676    empty_strings_allowed = True 
    7777 
    78     # Tracks each time a Field instance is created. Used to retain order. 
     78    # These track each time a Field instance is created. Used to retain order. 
     79    # The auto_creation_counter is used for fields that Django implicitly 
     80    # creates, creation_counter is used for all user-specified fields. 
    7981    creation_counter = 0 
     82    auto_creation_counter = -1 
    8083 
    8184    def __init__(self, verbose_name=None, name=None, primary_key=False, 
    82         max_length=None, unique=False, blank=False, null=False, db_index=False, 
    83         core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True, 
    84         prepopulate_from=None, unique_for_date=None, unique_for_month=None, 
    85         unique_for_year=None, validator_list=None, choices=None, radio_admin=None, 
    86         help_text='', db_column=None, db_tablespace=None): 
     85            max_length=None, unique=False, blank=False, null=False, 
     86            db_index=False, core=False, rel=None, default=NOT_PROVIDED, 
     87            editable=True, serialize=True, prepopulate_from=None, 
     88            unique_for_date=None, unique_for_month=None, unique_for_year=None, 
     89            validator_list=None, choices=None, radio_admin=None, help_text='', 
     90            db_column=None, db_tablespace=None, auto_created=False): 
    8791        self.name = name 
    8892        self.verbose_name = verbose_name 
     
    110114        self.db_index = db_index 
    111115 
    112         # Increase the creation counter, and save our local copy. 
    113         self.creation_counter = Field.creation_counter 
    114         Field.creation_counter += 1 
     116        # Adjust the appropriate creation counter, and save our local copy. 
     117        if auto_created: 
     118            self.creation_counter = Field.auto_creation_counter 
     119            Field.auto_creation_counter -= 1 
     120        else: 
     121            self.creation_counter = Field.creation_counter 
     122            Field.creation_counter += 1 
    115123 
    116124    def __cmp__(self, other): 
  • django/branches/queryset-refactor/django/db/models/fields/related.py

    r7096 r7126  
    495495        # signature of ManyToOneRel.__init__(). 
    496496        super(OneToOneRel, self).__init__(to, field_name, num_in_admin, 
    497                 edit_inline, related_name, limit_choices_to, lookup_overrides, 
    498                 raw_id_admin) 
     497                edit_inline=edit_inline, related_name=related_name, 
     498                limit_choices_to=limit_choices_to, 
     499                lookup_overrides=lookup_overrides, raw_id_admin=raw_id_admin) 
    499500        self.multiple = False 
    500501 
     
    755756    def save_form_data(self, instance, data): 
    756757        setattr(instance, self.attname, data) 
    757          
     758 
    758759    def formfield(self, **kwargs): 
    759760        defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()} 
  • django/branches/queryset-refactor/django/db/models/manager.py

    r7049 r7126  
    66def ensure_default_manager(sender): 
    77    cls = sender 
    8     if not hasattr(cls, '_default_manager')
     8    if not hasattr(cls, '_default_manager') or cls._default_manager is None
    99        # Create the default manager, if needed. 
    1010        try: 
     
    3232        self.model = model 
    3333        setattr(model, name, ManagerDescriptor(self)) 
    34         if not hasattr(model, '_default_manager') or self.creation_counter < model._default_manager.creation_counter: 
     34        if not hasattr(model, '_default_manager') or model._default_manager is None or self.creation_counter < model._default_manager.creation_counter: 
    3535            model._default_manager = self 
    3636 
  • django/branches/queryset-refactor/django/db/models/options.py

    r7048 r7126  
     1import re 
     2from bisect import bisect 
     3 
    14from django.conf import settings 
    25from django.db.models.related import RelatedObject 
     
    811from django.utils.translation import activate, deactivate_all, get_language, string_concat 
    912from django.utils.encoding import force_unicode, smart_str 
    10 from bisect import bisect 
    11 import re 
     13from django.utils.datastructures import SortedDict 
    1214 
    1315# Calculate the verbose_name by converting from InitialCaps to "lowercase with spaces". 
     
    1618DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', 
    1719                 'unique_together', 'permissions', 'get_latest_by', 
    18                  'order_with_respect_to', 'app_label', 'db_tablespace') 
     20                 'order_with_respect_to', 'app_label', 'db_tablespace', 
     21                 'abstract') 
    1922 
    2023class Options(object): 
    2124    def __init__(self, meta): 
    22         self.fields, self.many_to_many = [], [] 
     25        self.local_fields, self.local_many_to_many = [], [] 
    2326        self.module_name, self.verbose_name = None, None 
    2427        self.verbose_name_plural = None 
     
    3639        self.has_auto_field, self.auto_field = False, None 
    3740        self.one_to_one_field = None 
    38         self.parents = [] 
     41        self.abstract = False 
     42        self.parents = SortedDict() 
    3943 
    4044    def contribute_to_class(self, cls, name): 
     
    8387 
    8488        if self.pk is None: 
    85             auto = AutoField(verbose_name='ID', primary_key=True) 
    86             auto.creation_counter = -1 
    87             model.add_to_class('id', auto) 
     89            if self.parents: 
     90                # Promote the first parent link in lieu of adding yet another 
     91                # field. 
     92                field = self.parents.value_for_index(0) 
     93                field.primary_key = True 
     94                self.pk = field 
     95            else: 
     96                auto = AutoField(verbose_name='ID', primary_key=True, 
     97                        auto_created=True) 
     98                model.add_to_class('id', auto) 
    8899 
    89100        # If the db_table wasn't provided, use the app_label + module_name. 
     
    98109        # self.many_to_many. 
    99110        if field.rel and isinstance(field.rel, ManyToManyRel): 
    100             self.many_to_many.insert(bisect(self.many_to_many, field), field) 
     111            self.local_many_to_many.insert(bisect(self.local_many_to_many, field), field) 
    101112        else: 
    102             self.fields.insert(bisect(self.fields, field), field) 
     113            self.local_fields.insert(bisect(self.local_fields, field), field) 
    103114            if not self.pk and field.primary_key: 
    104115                self.pk = field 
    105116                field.serialize = False 
     117 
     118        # All of these internal caches need to be updated the next time they 
     119        # are used. 
     120        # TODO: Do this more neatly. (Also, use less caches!) 
     121        if hasattr(self, '_field_cache'): 
     122            del self._field_cache 
     123        if hasattr(self, '_m2m_cache'): 
     124            del self._m2m_cache 
     125        if hasattr(self, '_name_map'): 
     126            del self._name_map 
    106127 
    107128    def __repr__(self): 
     
    124145    verbose_name_raw = property(verbose_name_raw) 
    125146 
     147    def _fields(self): 
     148        """ 
     149        The getter for self.fields. This returns the list of field objects 
     150        available to this model (including through parent models). 
     151        """ 
     152        try: 
     153            self._field_cache 
     154        except AttributeError: 
     155            self._fill_fields_cache() 
     156        return self._field_cache.keys() 
     157    fields = property(_fields) 
     158 
     159    def get_fields_with_model(self): 
     160        """ 
     161        Returns a list of (field, model) pairs for all fields. The "model" 
     162        element is None for fields on the current model. Mostly of use when 
     163        constructing queries so that we know which model a field belongs to. 
     164        """ 
     165        try: 
     166            self._field_cache 
     167        except AttributeError: 
     168            self._fill_fields_cache() 
     169        return self._field_cache.items() 
     170 
     171    def _fill_fields_cache(self): 
     172        cache = SortedDict() 
     173        for parent in self.parents: 
     174            for field, model in parent._meta.get_fields_with_model(): 
     175                if model: 
     176                    cache[field] = model 
     177                else: 
     178                    cache[field] = parent 
     179        for field in self.local_fields: 
     180            cache[field] = None 
     181        self._field_cache = cache 
     182 
     183    def _many_to_many(self): 
     184        try: 
     185            self._m2m_cache 
     186        except AttributeError: 
     187            self._fill_m2m_cache() 
     188        return self._m2m_cache.keys() 
     189    many_to_many = property(_many_to_many) 
     190 
     191    def get_m2m_with_model(self): 
     192        """ 
     193        The many-to-many version of get_fields_with_model(). 
     194        """ 
     195        try: 
     196            self._m2m_cache 
     197        except AttributeError: 
     198            self._fill_m2m_cache() 
     199        return self._m2m_cache.items() 
     200 
     201    def _fill_m2m_cache(self): 
     202        cache = SortedDict() 
     203        for parent in self.parents: 
     204            for field, model in parent._meta.get_m2m_with_model(): 
     205                if model: 
     206                    cache[field] = model 
     207                else: 
     208                    cache[field] = parent 
     209        for field in self.local_many_to_many: 
     210            cache[field] = None 
     211        self._m2m_cache = cache 
     212 
    126213    def get_field(self, name, many_to_many=True): 
    127         "Returns the requested field by name. Raises FieldDoesNotExist on error." 
     214        """ 
     215        Returns the requested field by name. Raises FieldDoesNotExist on error. 
     216        """ 
    128217        to_search = many_to_many and (self.fields + self.many_to_many) or self.fields 
    129218        for f in to_search: 
     
    134223    def get_field_by_name(self, name, only_direct=False): 
    135224        """ 
    136         Returns the (field_object, direct, m2m), where field_object is the 
    137         Field instance for the given name, direct is True if the field exists 
     225        Returns the (field_object, model, direct, m2m), where field_object is 
     226        the Field instance for the given name, model is the model containing 
     227        this field (None for local fields), direct is True if the field exists 
    138228        on this model, and m2m is True for many-to-many relations. When 
    139229        'direct' is False, 'field_object' is the corresponding RelatedObject 
     
    152242            result = cache.get(name) 
    153243 
    154         if not result or (not result[1] and only_direct): 
     244        if not result or (only_direct and not result[2]): 
    155245            raise FieldDoesNotExist('%s has no field named %r' 
    156246                    % (self.object_name, name)) 
     
    174264        Initialises the field name -> field object mapping. 
    175265        """ 
    176         cache = dict([(f.name, (f, True, False)) for f in self.fields]) 
    177         for f in self.many_to_many: 
    178             cache[f.name] = (f, True, True) 
    179         for f in self.get_all_related_many_to_many_objects(): 
    180             cache[f.field.related_query_name()] = (f, False, True) 
    181         for f in self.get_all_related_objects(): 
    182             cache[f.field.related_query_name()] = (f, False, False) 
     266        cache = dict([(f.name, (f, m, True, False)) for f, m in 
     267                self.get_fields_with_model()]) 
     268        for f, model in self.get_m2m_with_model(): 
     269            cache[f.name] = (f, model, True, True) 
     270        for f, model in self.get_all_related_m2m_objects_with_model(): 
     271            cache[f.field.related_query_name()] = (f, model, False, True) 
     272        for f, model in self.get_all_related_objects_with_model(): 
     273            cache[f.field.related_query_name()] = (f, model, False, False) 
    183274        if self.order_with_respect_to: 
    184             cache['_order'] = OrderWrt(), True, False 
     275            cache['_order'] = OrderWrt(), None, True, False 
    185276        if app_cache_ready(): 
    186277            self._name_map = cache 
     
    196287        return 'delete_%s' % self.object_name.lower() 
    197288 
    198     def get_all_related_objects(self): 
    199         try: # Try the cache first. 
    200             return self._all_related_objects 
    201         except AttributeError: 
    202             rel_objs = [] 
    203             for klass in get_models(): 
    204                 for f in klass._meta.fields: 
    205                     if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: 
    206                         rel_objs.append(RelatedObject(f.rel.to, klass, f)) 
    207             self._all_related_objects = rel_objs 
    208             return rel_objs 
     289    def get_all_related_objects(self, local_only=False): 
     290        try: 
     291            self._related_objects_cache 
     292        except AttributeError: 
     293            self._fill_related_objects_cache() 
     294        if local_only: 
     295            return [k for k, v in self._related_objects_cache.items() if not v] 
     296        return self._related_objects_cache.keys() 
     297 
     298    def get_all_related_objects_with_model(self): 
     299        """ 
     300        Returns a list of (related-object, model) pairs. Similar to 
     301        get_fields_with_model(). 
     302        """ 
     303        try: 
     304            self._related_objects_cache 
     305        except AttributeError: 
     306            self._fill_related_objects_cache() 
     307        return self._related_objects_cache.items() 
     308 
     309    def _fill_related_objects_cache(self): 
     310        cache = SortedDict() 
     311        parent_list = self.get_parent_list() 
     312        for parent in self.parents: 
     313            for obj, model in parent._meta.get_all_related_objects_with_model(): 
     314                if obj.field.creation_counter < 0 and obj.model not in parent_list: 
     315                    continue 
     316                if not model: 
     317                    cache[obj] = parent 
     318                else: 
     319                    cache[obj] = model 
     320        for klass in get_models(): 
     321            for f in klass._meta.local_fields: 
     322                if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: 
     323                    cache[RelatedObject(f.rel.to, klass, f)] = None 
     324        self._related_objects_cache = cache 
     325 
     326    def get_all_related_many_to_many_objects(self, local_only=False): 
     327        try: 
     328            cache = self._related_many_to_many_cache 
     329        except AttributeError: 
     330            cache = self._fill_related_many_to_many_cache() 
     331        if local_only: 
     332            return [k for k, v in cache.items() if not v] 
     333        return cache.keys() 
     334 
     335    def get_all_related_m2m_objects_with_model(self): 
     336        """ 
     337        Returns a list of (related-m2m-object, model) pairs. Similar to 
     338        get_fields_with_model(). 
     339        """ 
     340        try: 
     341            cache = self._related_many_to_many_cache 
     342        except AttributeError: 
     343            cache = self._fill_related_many_to_many_cache() 
     344        return cache.items() 
     345 
     346    def _fill_related_many_to_many_cache(self): 
     347        cache = SortedDict() 
     348        parent_list = self.get_parent_list() 
     349        for parent in self.parents: 
     350            for obj, model in parent._meta.get_all_related_m2m_objects_with_model(): 
     351                if obj.field.creation_counter < 0 and obj.model not in parent_list: 
     352                    continue 
     353                if not model: 
     354                    cache[obj] = parent 
     355                else: 
     356                    cache[obj] = model 
     357        for klass in get_models(): 
     358            for f in klass._meta.local_many_to_many: 
     359                if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: 
     360                    cache[RelatedObject(f.rel.to, klass, f)] = None 
     361        if app_cache_ready(): 
     362            self._related_many_to_many_cache = cache 
     363        return cache 
    209364 
    210365    def get_followed_related_objects(self, follow=None): 
     
    230385        return follow 
    231386 
    232     def get_all_related_many_to_many_objects(self): 
    233         try: # Try the cache first. 
    234             return self._all_related_many_to_many_objects 
    235         except AttributeError: 
    236             rel_objs = [] 
    237             for klass in get_models(): 
    238                 for f in klass._meta.many_to_many: 
    239                     if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta: 
    240                         rel_objs.append(RelatedObject(f.rel.to, klass, f)) 
    241             if app_cache_ready(): 
    242                 self._all_related_many_to_many_objects = rel_objs 
    243             return rel_objs 
     387    def get_base_chain(self, model): 
     388        """ 
     389        Returns a list of parent classes leading to 'model' (order from closet 
     390        to most distant ancestor). This has to handle the case were 'model' is 
     391        a granparent or even more distant relation. 
     392        """ 
     393        if not self.parents: 
     394            return 
     395        if model in self.parents: 
     396            return [model] 
     397        for parent in self.parents: 
     398            res = parent._meta.get_base_chain(model) 
     399            if res: 
     400                res.insert(0, parent) 
     401                return res 
     402        raise TypeError('%r is not an ancestor of this model' 
     403                % model._meta.module_name) 
     404 
     405    def get_parent_list(self): 
     406        """ 
     407        Returns a list of all the ancestor of this model as a list. Useful for 
     408        determining if something is an ancestor, regardless of lineage. 
     409        """ 
     410        # FIXME: Fix model hashing and then use a Set here. 
     411        result = [] 
     412        for parent in self.parents: 
     413            result.append(parent) 
     414            result.extend(parent._meta.get_parent_list()) 
     415        return result 
    244416 
    245417    def get_ordered_objects(self): 
  • django/branches/queryset-refactor/django/db/models/sql/query.py

    r7048 r7126  
    390390        elif self.default_cols: 
    391391            table_alias = self.tables[0] 
    392             result = ['%s.%s' % (qn(table_alias), qn(f.column)) 
    393                     for f in self.model._meta.fields] 
     392            root_pk = self.model._meta.pk.column 
     393            seen = {None: table_alias} 
     394            for field, model in self.model._meta.get_fields_with_model(): 
     395                if model not in seen: 
     396                    seen[model] = self.join((table_alias, model._meta.db_table, 
     397                            root_pk, model._meta.pk.column)) 
     398                result.append('%s.%s' % (qn(seen[model]), qn(field.column))) 
    394399            aliases = result[:] 
    395400 
     
    743748        alias = self.join((None, opts.db_table, None, None)) 
    744749 
    745         field, target, unused, join_list, = self.setup_joins(parts, opts, 
     750        field, target, opts, join_list, = self.setup_joins(parts, opts, 
    746751                alias, (connector == AND)) 
    747752        col = target.column 
     
    851856 
    852857            try: 
    853                 field, direct, m2m = opts.get_field_by_name(name) 
     858                field, model, direct, m2m = opts.get_field_by_name(name) 
    854859            except FieldDoesNotExist: 
    855860                names = opts.get_all_field_names() 
    856861                raise TypeError("Cannot resolve keyword %r into field. " 
    857862                        "Choices are: %s" % (name, ", ".join(names))) 
     863            if model: 
     864                # The field lives on a base class of the current model. 
     865                alias_list = [] 
     866                for int_model in opts.get_base_chain(model): 
     867                    lhs_col = opts.parents[int_model].column 
     868                    opts = int_model._meta 
     869                    alias = self.join((alias, opts.db_table, lhs_col, 
     870                            opts.pk.column)) 
     871                    alias_list.append(alias) 
     872                joins.append(alias_list) 
    858873            cached_data = opts._join_cache.get(name) 
    859874            orig_opts = opts 
     
    900915                    joins.append([alias]) 
    901916                else: 
     917                    # Non-relation fields. 
    902918                    target = field 
    903919                    break 
     
    12431259        from django.db.models.base import Model 
    12441260        for name, val in values.items(): 
    1245             field, direct, m2m = self.model._meta.get_field_by_name(name) 
     1261            field, model, direct, m2m = self.model._meta.get_field_by_name(name) 
    12461262            if not direct or m2m: 
    12471263                # Can only update non-relation fields and foreign keys. 
  • django/branches/queryset-refactor/docs/model-api.txt

    r7124 r7126  
    20292029.. _database API docs: ../db-api/ 
    20302030 
     2031Model inheritance 
     2032================= 
     2033 
     2034Abstract base classes 
     2035--------------------- 
     2036 
     2037Abstract base classes are useful when you want to put some common information 
     2038into a number of other models. You write your base class and put 
     2039``abstract=True`` in the ``Meta`` class