Ticket #10356: proxy_models.diff

File proxy_models.diff, 13.2 KB (added by Ryan Kelly, 15 years ago)

patch implementing proxy models

  • django/db/models/sql/query.py

     
    639639            try:
    640640                alias = seen[model]
    641641            except KeyError:
    642                 alias = self.join((table_alias, model._meta.db_table,
    643                         root_pk, model._meta.pk.column))
     642                #  Don't create a join if the subclass is using the
     643                #  same table as its superclass.
     644                if table_alias == model._meta.db_table and root_pk == model._meta.pk.column:
     645                    alias = table_alias
     646                else:
     647                    alias = self.join((table_alias, model._meta.db_table,
     648                            root_pk, model._meta.pk.column))
    644649                seen[model] = alias
    645650            if as_pairs:
    646651                result.append((alias, field.column))
     
    15091514                                (id(opts), lhs_col), ()))
    15101515                        dupe_set.add((opts, lhs_col))
    15111516                    opts = int_model._meta
    1512                     alias = self.join((alias, opts.db_table, lhs_col,
    1513                             opts.pk.column), exclusions=exclusions)
    1514                     joins.append(alias)
    1515                     exclusions.add(alias)
     1517                    #  Don't create a join if the subclass is using the
     1518                    #  same table as its superclass.
     1519                    if alias != opts.db_table or lhs_col != opts.pk.column:
     1520                        alias = self.join((alias, opts.db_table, lhs_col,
     1521                                opts.pk.column), exclusions=exclusions)
     1522                        joins.append(alias)
     1523                        exclusions.add(alias)
    15161524                    for (dupe_opts, dupe_col) in dupe_set:
    15171525                        self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
    15181526            cached_data = opts._join_cache.get(name)
  • django/db/models/base.py

     
    7979        for obj_name, obj in attrs.items():
    8080            new_class.add_to_class(obj_name, obj)
    8181
     82        # All the fields of any type declared on this model
     83        new_fields = new_class._meta.local_fields + \
     84                     new_class._meta.local_many_to_many + \
     85                     new_class._meta.virtual_fields
     86        field_names = set([f.name for f in new_fields])
     87
    8288        # Do the appropriate setup for any model parents.
    8389        o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
    8490                if isinstance(f, OneToOneField)])
     91
     92        # Proxy models must have at least one concrete base class.
     93        if new_class._meta.proxy:
     94            for base in parents:
     95                if hasattr(base, '_meta') and not base._meta.abstract:
     96                    break
     97            else:
     98                raise TypeError('Proxy models must have at least one '\
     99                                'non-abstract base class')
     100
    85101        for base in parents:
    86102            if not hasattr(base, '_meta'):
    87103                # Things without _meta aren't functional models, so they're
    88104                # uninteresting parents.
    89105                continue
    90106
    91             # All the fields of any type declared on this model
    92             new_fields = new_class._meta.local_fields + \
    93                          new_class._meta.local_many_to_many + \
    94                          new_class._meta.virtual_fields
    95             field_names = set([f.name for f in new_fields])
    96 
    97107            if not base._meta.abstract:
    98108                # Concrete classes...
    99                 if base in o2o_map:
     109                if new_class._meta.proxy:
     110                    if new_fields:
     111                        raise FieldError('Proxy models cannot define '\
     112                                         'new fields')
     113                    field = base._meta.pk
     114                    new_class._meta.managed = False
     115                    new_class._meta.db_table = base._meta.db_table
     116                    new_class._meta.pk = field
     117                elif base in o2o_map:
    100118                    field = o2o_map[base]
    101119                    field.primary_key = True
    102120                    new_class._meta.setup_pk(field)
     
    110128            else:
    111129                # .. and abstract ones.
    112130
     131                if new_class._meta.proxy:
     132                    raise TypeError('Proxy models cannot have '\
     133                                    'abstract base classes')
    113134                # Check for clashes between locally declared fields and those
    114135                # on the ABC.
    115136                parent_fields = base._meta.local_fields + base._meta.local_many_to_many
  • django/db/models/options.py

     
    2121DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
    2222                 'unique_together', 'permissions', 'get_latest_by',
    2323                 'order_with_respect_to', 'app_label', 'db_tablespace',
    24                  'abstract')
     24                 'abstract', 'proxy')
    2525
    2626class Options(object):
    2727    def __init__(self, meta, app_label=None):
     
    4242        self.pk = None
    4343        self.has_auto_field, self.auto_field = False, None
    4444        self.abstract = False
     45        self.proxy = False
    4546        self.parents = SortedDict()
    4647        self.duplicate_targets = {}
    4748        # Managers that have been inherited from abstract base classes. These
  • tests/modeltests/proxy_models/__init__.py

    Property changes on: tests/modeltests/proxy_models
    ___________________________________________________________________
    Added: svn:mergeinfo
    
     
     1
     2
  • tests/modeltests/proxy_models/models.py

     
     1"""
     2xx. proxy_models
     3 
     4By specifying the 'proxy' Meta attribute, model subclasses can specify that
     5they will take data directly from their base class's table rather than using
     6a new table of their own. This allows them to act as simple proxies, providing
     7a modified interface to the data from the base class.
     8"""
     9 
     10from django.db import models
     11 
     12
     13class Person(models.Model):
     14    """A simple concrete base class."""
     15    name = models.CharField(max_length=50)
     16
     17
     18class Abstract(models.Model):
     19    """A simple abstract base class, to be used for error checking."""
     20    class Meta:
     21        abstract = True
     22
     23
     24class Value(Abstract):
     25    """A second concrente base class, for testing multiple inheritance."""
     26    value = models.CharField(max_length=10)
     27    def __unicode__(self):
     28        return self.value
     29
     30
     31class MyPerson(Person):
     32    """A proxy subclass, this should not get a new table."""
     33    class Meta:
     34        proxy = True
     35    def has_special_name(self):
     36        """Check whether this person has a special name."""
     37        if self.name.lower() == "special":
     38            return True
     39        return False
     40       
     41
     42class StatusPerson(MyPerson):
     43    """A non-proxy subclass of a proxy, it should get a new table."""
     44    status = models.CharField(max_length=80)
     45
     46
     47class ValuePerson(Person,Value):
     48    """Multiple-inheritance proxy subclass."""
     49    class Meta:
     50        proxy = True
     51
     52
     53__test__ = {'API_TESTS' : """
     54# The MyPerson class should be using the main Person table
     55>>> MyPerson._meta.db_table == Person._meta.db_table
     56True
     57 
     58# The StatusPerson class should get its own table
     59>>> StatusPerson._meta.db_table != Person._meta.db_table
     60True
     61 
     62# Creating a Person makes them accessable through the MyPerson proxy
     63>>> Person(name="Foo McBar").save()
     64>>> len(Person.objects.all())
     651
     66>>> len(MyPerson.objects.all())
     671
     68>>> MyPerson.objects.get(name="Foo McBar").id
     691
     70>>> MyPerson.objects.get(id=1).has_special_name()
     71False
     72
     73# But not through the StatusPerson subclass
     74>>> StatusPerson.objects.all()
     75[]
     76
     77# A new MyPerson also shows up as a standard Person
     78>>> MyPerson(name="Bazza del Frob").save()
     79>>> len(MyPerson.objects.all())
     802
     81>>> len(Person.objects.all())
     822
     83
     84# Since these people don't have a corresponding record in the Value table,
     85# they are ignored by the ValuePerson proxy
     86>>> ValuePerson.objects.all()
     87[]
     88
     89# But if we insert a matching value record, they will show up
     90>>> Value.objects.create(id=1,value="fourty-two")
     91<Value: fourty-two>
     92>>> len(Value.objects.all())
     931
     94>>> len(ValuePerson.objects.all())
     951
     96>>> ValuePerson.objects.get(id=1).name
     97u"Foo McBar"
     98>>> ValuePerson.objects.get(id=1).value
     99u"fourty-two"
     100
     101# And now for some things that shouldn't work...
     102#
     103# All base classes must be non-abstract
     104>>> class NoAbstract(Person,Abstract):
     105...     class Meta:
     106...         proxy = True
     107Traceback (most recent call last):
     108    ....
     109TypeError: Proxy models cannot have abstract base classes
     110
     111# The proxy must actually have at least one concrete base class
     112>>> class NoBaseClasses(models.Model):
     113...     class Meta:
     114...         proxy = True
     115Traceback (most recent call last):
     116    ....
     117TypeError: Proxy models must have at least one non-abstract base class
     118
     119# A proxy cannot introduce any new fields
     120>>> class NoNewFields(Person):
     121...     class Meta:
     122...         proxy = True
     123...     newfield = models.BooleanField()
     124Traceback (most recent call last):
     125    ....
     126FieldError: Proxy models cannot define new fields
     127"""}
     128
  • docs/topics/db/models.txt

     
    776776Often, you will just want to use the parent class to hold information
    777777that you don't want to have to type out for each child model. This
    778778class isn't going to ever be used in isolation, so
    779 :ref:`abstract-base-classes` are what you're after. However, if you're
    780 subclassing an existing model (perhaps something from another
     779:ref:`abstract-base-classes` are what you're after. If you're
     780adding information to an existing model (perhaps something from another
    781781application entirely), or want each model to have its own database
    782 table, :ref:`multi-table-inheritance` is the way to go.
     782table, :ref:`multi-table-inheritance` is the way to go.  Finally, if you
     783want to modify the way a model behaves but are not storing any additional
     784model data, you can make your subclass a :ref:`Proxy model <proxy-models>`
     785to avoid creating a new database table.
    783786
     787
     788
    784789.. _abstract-base-classes:
    785790
    786791Abstract base classes
     
    990995:attr:`parent_link=True <django.db.models.fields.OneToOneField.parent_link>`
    991996to indicate that your field is the link back to the parent class.
    992997
     998.. _proxy-models:
     999
     1000Proxy models
     1001------------
     1002
     1003.. versionadded:: 1.1
     1004
     1005When using :ref:`multi-table inheritance <multi-table-inheritance>`, a new
     1006database table is created for each subclass of a model.  This is usually the
     1007desired behaviour, since the subclass needs a place to store any additional
     1008data fields that are not present on the base class.  However, it is also useful
     1009to be able to subclass a model *without* introducing a new database table.
     1010This is what proxy models are designed to achieve.
     1011
     1012For example, suppose we want to add a method to the standard ``User`` model that
     1013will look up some additional information from another source.  This does not
     1014require a new database table - rather, we want to access the entries in the
     1015standard user table through a customised interface.  We would mark our
     1016``User`` subclass as a proxy model as follows::
     1017
     1018    class MyUser(User):
     1019
     1020        def fetch_additional_data(self):
     1021            # ...fetch some additional data...
     1022            return data
     1023
     1024        class Meta:
     1025            proxy = True
     1026
     1027The ``MyUser`` class would then operate on the same database table as its
     1028parent ``User`` class.  In particular, any new instances of ``User`` will
     1029also be accessible through ``MyUser``, and vice-versa::
     1030
     1031    >>> u = User.objects.create(username="foobar")
     1032    >>> MyUser.objects.get(username="foobar")
     1033    <MyUser: foobar>
     1034
     1035Since no database table is created, proxy models cannot define any
     1036additional data fields and do not get an automatic ``OneToOneField`` linking to
     1037their parent class.  In all other respects (e.g. inheritance of the
     1038:ref:`Meta <meta-options>` class) they behave identically to standard
     1039:ref:`multi-table inheritance <multi-table-inheritance>` subclasses.
     1040
     1041
    9931042Multiple inheritance
    9941043--------------------
    9951044
  • docs/ref/models/options.txt

     
    1919
    2020If ``True``, this model will be an :ref:`abstract base class <abstract-base-classes>`.
    2121
     22``proxy``
     23-----------------
     24
     25.. attribute:: Options.proxy
     26
     27.. versionadded:: 1.1
     28
     29If ``True``, this model will be a :ref:`proxy model <proxy-models>`.
     30
     31
    2232``db_table``
    2333------------
    2434
Back to Top