Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #10356 -- Added pure-Python inheritance for models (a.k.a proxy…

… models).

Large portions of this are needed for #5420, so I implemented it fully.
Thanks to Ryan Kelly for an initial patch to get this started.

Refs #5420.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10083 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 61a2708c4108939795c70cf124d5696275d6c255 1 parent c0b6e23
Malcolm Tredinnick authored March 18, 2009
1  AUTHORS
@@ -220,6 +220,7 @@ answer newbie questions, and generally made Django that much better:
220 220
     Erik Karulf <erik@karulf.com>
221 221
     Ben Dean Kawamura <ben.dean.kawamura@gmail.com>
222 222
     Ian G. Kelly <ian.g.kelly@gmail.com>
  223
+    Ryan Kelly <ryan@rfk.id.au>
223 224
     Thomas Kerpe <thomas@kerpe.net>
224 225
     Ossama M. Khayat <okhayat@yahoo.com>
225 226
     Ben Khoo <khoobks@westnet.com.au>
170  django/db/models/base.py
@@ -67,9 +67,19 @@ def __new__(cls, name, bases, attrs):
67 67
                 if not hasattr(meta, 'get_latest_by'):
68 68
                     new_class._meta.get_latest_by = base_meta.get_latest_by
69 69
 
  70
+        is_proxy = new_class._meta.proxy
  71
+
70 72
         if getattr(new_class, '_default_manager', None):
71  
-            new_class._default_manager = None
72  
-            new_class._base_manager = None
  73
+            if not is_proxy:
  74
+                # Multi-table inheritance doesn't inherit default manager from
  75
+                # parents.
  76
+                new_class._default_manager = None
  77
+                new_class._base_manager = None
  78
+            else:
  79
+                # Proxy classes do inherit parent's default manager, if none is
  80
+                # set explicitly.
  81
+                new_class._default_manager = new_class._default_manager._copy_to_model(new_class)
  82
+                new_class._base_manager = new_class._base_manager._copy_to_model(new_class)
73 83
 
74 84
         # Bail out early if we have already created this class.
75 85
         m = get_model(new_class._meta.app_label, name, False)
@@ -80,21 +90,43 @@ def __new__(cls, name, bases, attrs):
80 90
         for obj_name, obj in attrs.items():
81 91
             new_class.add_to_class(obj_name, obj)
82 92
 
  93
+        # All the fields of any type declared on this model
  94
+        new_fields = new_class._meta.local_fields + \
  95
+                     new_class._meta.local_many_to_many + \
  96
+                     new_class._meta.virtual_fields
  97
+        field_names = set([f.name for f in new_fields])
  98
+
  99
+        # Basic setup for proxy models.
  100
+        if is_proxy:
  101
+            base = None
  102
+            for parent in [cls for cls in parents if hasattr(cls, '_meta')]:
  103
+                if parent._meta.abstract:
  104
+                    if parent._meta.fields:
  105
+                        raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name)
  106
+                    else:
  107
+                        continue
  108
+                if base is not None:
  109
+                    raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name)
  110
+                else:
  111
+                    base = parent
  112
+            if base is None:
  113
+                    raise TypeError("Proxy model '%s' has no non-abstract model base class." % name)
  114
+            if (new_class._meta.local_fields or
  115
+                    new_class._meta.local_many_to_many):
  116
+                raise FieldError("Proxy model '%s' contains model fields."
  117
+                        % name)
  118
+            new_class._meta.setup_proxy(base)
  119
+
83 120
         # Do the appropriate setup for any model parents.
84 121
         o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
85 122
                 if isinstance(f, OneToOneField)])
  123
+
86 124
         for base in parents:
87 125
             if not hasattr(base, '_meta'):
88 126
                 # Things without _meta aren't functional models, so they're
89 127
                 # uninteresting parents.
90 128
                 continue
91 129
 
92  
-            # All the fields of any type declared on this model
93  
-            new_fields = new_class._meta.local_fields + \
94  
-                         new_class._meta.local_many_to_many + \
95  
-                         new_class._meta.virtual_fields
96  
-            field_names = set([f.name for f in new_fields])
97  
-
98 130
             parent_fields = base._meta.local_fields + base._meta.local_many_to_many
99 131
             # Check for clashes between locally declared fields and those
100 132
             # on the base classes (we cannot handle shadowed fields at the
@@ -107,15 +139,19 @@ def __new__(cls, name, bases, attrs):
107 139
                                         (field.name, name, base.__name__))
108 140
             if not base._meta.abstract:
109 141
                 # Concrete classes...
  142
+                while base._meta.proxy:
  143
+                    # Skip over a proxy class to the "real" base it proxies.
  144
+                    base = base._meta.proxy_for_model
110 145
                 if base in o2o_map:
111 146
                     field = o2o_map[base]
112  
-                else:
  147
+                elif not is_proxy:
113 148
                     attr_name = '%s_ptr' % base._meta.module_name
114 149
                     field = OneToOneField(base, name=attr_name,
115 150
                             auto_created=True, parent_link=True)
116 151
                     new_class.add_to_class(attr_name, field)
  152
+                else:
  153
+                    field = None
117 154
                 new_class._meta.parents[base] = field
118  
-
119 155
             else:
120 156
                 # .. and abstract ones.
121 157
                 for field in parent_fields:
@@ -125,13 +161,12 @@ def __new__(cls, name, bases, attrs):
125 161
                 new_class._meta.parents.update(base._meta.parents)
126 162
 
127 163
             # Inherit managers from the abstract base classes.
128  
-            base_managers = base._meta.abstract_managers
129  
-            base_managers.sort()
130  
-            for _, mgr_name, manager in base_managers:
131  
-                val = getattr(new_class, mgr_name, None)
132  
-                if not val or val is manager:
133  
-                    new_manager = manager._copy_to_model(new_class)
134  
-                    new_class.add_to_class(mgr_name, new_manager)
  164
+            new_class.copy_managers(base._meta.abstract_managers)
  165
+
  166
+            # Proxy models inherit the non-abstract managers from their base,
  167
+            # unless they have redefined any of them.
  168
+            if is_proxy:
  169
+                new_class.copy_managers(base._meta.concrete_managers)
135 170
 
136 171
             # Inherit virtual fields (like GenericForeignKey) from the parent
137 172
             # class
@@ -160,6 +195,15 @@ def __new__(cls, name, bases, attrs):
160 195
         # registered version.
161 196
         return get_model(new_class._meta.app_label, name, False)
162 197
 
  198
+    def copy_managers(cls, base_managers):
  199
+        # This is in-place sorting of an Options attribute, but that's fine.
  200
+        base_managers.sort()
  201
+        for _, mgr_name, manager in base_managers:
  202
+            val = getattr(cls, mgr_name, None)
  203
+            if not val or val is manager:
  204
+                new_manager = manager._copy_to_model(cls)
  205
+                cls.add_to_class(mgr_name, new_manager)
  206
+
163 207
     def add_to_class(cls, name, value):
164 208
         if hasattr(value, 'contribute_to_class'):
165 209
             value.contribute_to_class(cls, name)
@@ -358,55 +402,59 @@ def save_base(self, raw=False, cls=None, force_insert=False,
358 402
                 # At this point, parent's primary key field may be unknown
359 403
                 # (for example, from administration form which doesn't fill
360 404
                 # this field). If so, fill it.
361  
-                if getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
  405
+                if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
362 406
                     setattr(self, parent._meta.pk.attname, getattr(self, field.attname))
363 407
 
364  
-                self.save_base(raw, parent)
365  
-                setattr(self, field.attname, self._get_pk_val(parent._meta))
366  
-
367  
-        non_pks = [f for f in meta.local_fields if not f.primary_key]
368  
-
369  
-        # First, try an UPDATE. If that doesn't update anything, do an INSERT.
370  
-        pk_val = self._get_pk_val(meta)
371  
-        pk_set = pk_val is not None
372  
-        record_exists = True
373  
-        manager = cls._base_manager
374  
-        if pk_set:
375  
-            # Determine whether a record with the primary key already exists.
376  
-            if (force_update or (not force_insert and
377  
-                    manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())):
378  
-                # It does already exist, so do an UPDATE.
379  
-                if force_update or non_pks:
380  
-                    values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
381  
-                    rows = manager.filter(pk=pk_val)._update(values)
382  
-                    if force_update and not rows:
383  
-                        raise DatabaseError("Forced update did not affect any rows.")
384  
-            else:
385  
-                record_exists = False
386  
-        if not pk_set or not record_exists:
387  
-            if not pk_set:
388  
-                if force_update:
389  
-                    raise ValueError("Cannot force an update in save() with no primary key.")
390  
-                values = [(f, 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)]
391  
-            else:
392  
-                values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields]
  408
+                self.save_base(cls=parent)
  409
+                if field:
  410
+                    setattr(self, field.attname, self._get_pk_val(parent._meta))
  411
+            if meta.proxy:
  412
+                return
  413
+
  414
+        if not meta.proxy:
  415
+            non_pks = [f for f in meta.local_fields if not f.primary_key]
  416
+
  417
+            # First, try an UPDATE. If that doesn't update anything, do an INSERT.
  418
+            pk_val = self._get_pk_val(meta)
  419
+            pk_set = pk_val is not None
  420
+            record_exists = True
  421
+            manager = cls._base_manager
  422
+            if pk_set:
  423
+                # Determine whether a record with the primary key already exists.
  424
+                if (force_update or (not force_insert and
  425
+                        manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())):
  426
+                    # It does already exist, so do an UPDATE.
  427
+                    if force_update or non_pks:
  428
+                        values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
  429
+                        rows = manager.filter(pk=pk_val)._update(values)
  430
+                        if force_update and not rows:
  431
+                            raise DatabaseError("Forced update did not affect any rows.")
  432
+                else:
  433
+                    record_exists = False
  434
+            if not pk_set or not record_exists:
  435
+                if not pk_set:
  436
+                    if force_update:
  437
+                        raise ValueError("Cannot force an update in save() with no primary key.")
  438
+                    values = [(f, 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)]
  439
+                else:
  440
+                    values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields]
393 441
 
394  
-            if meta.order_with_respect_to:
395  
-                field = meta.order_with_respect_to
396  
-                values.append((meta.get_field_by_name('_order')[0], manager.filter(**{field.name: getattr(self, field.attname)}).count()))
397  
-            record_exists = False
  442
+                if meta.order_with_respect_to:
  443
+                    field = meta.order_with_respect_to
  444
+                    values.append((meta.get_field_by_name('_order')[0], manager.filter(**{field.name: getattr(self, field.attname)}).count()))
  445
+                record_exists = False
398 446
 
399  
-            update_pk = bool(meta.has_auto_field and not pk_set)
400  
-            if values:
401  
-                # Create a new record.
402  
-                result = manager._insert(values, return_id=update_pk)
403  
-            else:
404  
-                # Create a new record with defaults for everything.
405  
-                result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True)
  447
+                update_pk = bool(meta.has_auto_field and not pk_set)
  448
+                if values:
  449
+                    # Create a new record.
  450
+                    result = manager._insert(values, return_id=update_pk)
  451
+                else:
  452
+                    # Create a new record with defaults for everything.
  453
+                    result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True)
406 454
 
407  
-            if update_pk:
408  
-                setattr(self, meta.pk.attname, result)
409  
-        transaction.commit_unless_managed()
  455
+                if update_pk:
  456
+                    setattr(self, meta.pk.attname, result)
  457
+            transaction.commit_unless_managed()
410 458
 
411 459
         if signal:
412 460
             signals.post_save.send(sender=self.__class__, instance=self,
3  django/db/models/manager.py
@@ -60,6 +60,9 @@ def contribute_to_class(self, model, name):
60 60
         if model._meta.abstract or self._inherited:
61 61
             model._meta.abstract_managers.append((self.creation_counter, name,
62 62
                     self))
  63
+        else:
  64
+            model._meta.concrete_managers.append((self.creation_counter, name,
  65
+                self))
63 66
 
64 67
     def _set_creation_counter(self):
65 68
         """
19  django/db/models/options.py
@@ -21,7 +21,7 @@
21 21
 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
22 22
                  'unique_together', 'permissions', 'get_latest_by',
23 23
                  'order_with_respect_to', 'app_label', 'db_tablespace',
24  
-                 'abstract', 'managed')
  24
+                 'abstract', 'managed', 'proxy')
25 25
 
26 26
 class Options(object):
27 27
     def __init__(self, meta, app_label=None):
@@ -43,11 +43,15 @@ def __init__(self, meta, app_label=None):
43 43
         self.has_auto_field, self.auto_field = False, None
44 44
         self.abstract = False
45 45
         self.managed = True
  46
+        self.proxy = False
  47
+        self.proxy_for_model = None
46 48
         self.parents = SortedDict()
47 49
         self.duplicate_targets = {}
48  
-        # Managers that have been inherited from abstract base classes. These
49  
-        # are passed onto any children.
  50
+
  51
+        # To handle various inheritance situations, we need to track where
  52
+        # managers came from (concrete or abstract base classes).
50 53
         self.abstract_managers = []
  54
+        self.concrete_managers = []
51 55
 
52 56
     def contribute_to_class(self, cls, name):
53 57
         from django.db import connection
@@ -164,6 +168,15 @@ def setup_pk(self, field):
164 168
             self.pk = field
165 169
             field.serialize = False
166 170
 
  171
+    def setup_proxy(self, target):
  172
+        """
  173
+        Does the internal setup so that the current model is a proxy for
  174
+        "target".
  175
+        """
  176
+        self.pk = target._meta.pk
  177
+        self.proxy_for_model = target
  178
+        self.db_table = target._meta.db_table
  179
+
167 180
     def __repr__(self):
168 181
         return '<Options for %s>' % self.object_name
169 182
 
51  django/db/models/sql/query.py
@@ -641,6 +641,7 @@ def get_default_columns(self, with_aliases=False, col_aliases=None,
641 641
         qn = self.quote_name_unless_alias
642 642
         qn2 = self.connection.ops.quote_name
643 643
         aliases = set()
  644
+        proxied_model = opts.proxy and opts.proxy_for_model or 0
644 645
         if start_alias:
645 646
             seen = {None: start_alias}
646 647
         for field, model in opts.get_fields_with_model():
@@ -648,9 +649,12 @@ def get_default_columns(self, with_aliases=False, col_aliases=None,
648 649
                 try:
649 650
                     alias = seen[model]
650 651
                 except KeyError:
651  
-                    link_field = opts.get_ancestor_link(model)
652  
-                    alias = self.join((start_alias, model._meta.db_table,
653  
-                            link_field.column, model._meta.pk.column))
  652
+                    if model is proxied_model:
  653
+                        alias = start_alias
  654
+                    else:
  655
+                        link_field = opts.get_ancestor_link(model)
  656
+                        alias = self.join((start_alias, model._meta.db_table,
  657
+                                link_field.column, model._meta.pk.column))
654 658
                     seen[model] = alias
655 659
             else:
656 660
                 # If we're starting from the base model of the queryset, the
@@ -1158,11 +1162,15 @@ def setup_inherited_models(self):
1158 1162
         opts = self.model._meta
1159 1163
         root_alias = self.tables[0]
1160 1164
         seen = {None: root_alias}
  1165
+        proxied_model = opts.proxy and opts.proxy_for_model or 0
1161 1166
         for field, model in opts.get_fields_with_model():
1162 1167
             if model not in seen:
1163  
-                link_field = opts.get_ancestor_link(model)
1164  
-                seen[model] = self.join((root_alias, model._meta.db_table,
1165  
-                        link_field.column, model._meta.pk.column))
  1168
+                if model is proxied_model:
  1169
+                    seen[model] = root_alias
  1170
+                else:
  1171
+                    link_field = opts.get_ancestor_link(model)
  1172
+                    seen[model] = self.join((root_alias, model._meta.db_table,
  1173
+                            link_field.column, model._meta.pk.column))
1166 1174
         self.included_inherited_models = seen
1167 1175
 
1168 1176
     def remove_inherited_models(self):
@@ -1559,20 +1567,25 @@ def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
1559 1567
                 raise MultiJoin(pos + 1)
1560 1568
             if model:
1561 1569
                 # The field lives on a base class of the current model.
  1570
+                proxied_model = opts.proxy and opts.proxy_for_model or 0
1562 1571
                 for int_model in opts.get_base_chain(model):
1563  
-                    lhs_col = opts.parents[int_model].column
1564  
-                    dedupe = lhs_col in opts.duplicate_targets
1565  
-                    if dedupe:
1566  
-                        exclusions.update(self.dupe_avoidance.get(
1567  
-                                (id(opts), lhs_col), ()))
1568  
-                        dupe_set.add((opts, lhs_col))
1569  
-                    opts = int_model._meta
1570  
-                    alias = self.join((alias, opts.db_table, lhs_col,
1571  
-                            opts.pk.column), exclusions=exclusions)
1572  
-                    joins.append(alias)
1573  
-                    exclusions.add(alias)
1574  
-                    for (dupe_opts, dupe_col) in dupe_set:
1575  
-                        self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
  1572
+                    if int_model is proxied_model:
  1573
+                        opts = int_model._meta
  1574
+                    else:
  1575
+                        lhs_col = opts.parents[int_model].column
  1576
+                        dedupe = lhs_col in opts.duplicate_targets
  1577
+                        if dedupe:
  1578
+                            exclusions.update(self.dupe_avoidance.get(
  1579
+                                    (id(opts), lhs_col), ()))
  1580
+                            dupe_set.add((opts, lhs_col))
  1581
+                        opts = int_model._meta
  1582
+                        alias = self.join((alias, opts.db_table, lhs_col,
  1583
+                                opts.pk.column), exclusions=exclusions)
  1584
+                        joins.append(alias)
  1585
+                        exclusions.add(alias)
  1586
+                        for (dupe_opts, dupe_col) in dupe_set:
  1587
+                            self.update_dupe_avoidance(dupe_opts, dupe_col,
  1588
+                                    alias)
1576 1589
             cached_data = opts._join_cache.get(name)
1577 1590
             orig_opts = opts
1578 1591
             dupe_col = direct and field.column or field.field.column
10  docs/ref/models/options.txt
@@ -162,6 +162,16 @@ that has ``admin`` set. This example specifies an extra permission,
162 162
 This is a list or tuple of 2-tuples in the format ``(permission_code,
163 163
 human_readable_permission_name)``.
164 164
 
  165
+``proxy``
  166
+---------
  167
+
  168
+.. attribute:: Options.proxy
  169
+
  170
+.. versionadded: 1.1
  171
+
  172
+If set to ``True``, a model which subclasses another model will be treated as
  173
+a :ref:`proxy model <proxy-models>`.
  174
+
165 175
 ``unique_together``
166 176
 -------------------
167 177
 
2  docs/topics/db/managers.txt
@@ -195,6 +195,8 @@ attribute on the manager class. This is documented fully below_.
195 195
 
196 196
 .. _below: manager-types_
197 197
 
  198
+.. _custom-managers-and-inheritance:
  199
+
198 200
 Custom managers and model inheritance
199 201
 -------------------------------------
200 202
 
151  docs/topics/db/models.txt
@@ -773,13 +773,18 @@ is whether you want the parent models to be models in their own right
773 773
 of common information that will only be visible through the child
774 774
 models.
775 775
 
776  
-Often, you will just want to use the parent class to hold information
777  
-that you don't want to have to type out for each child model. This
778  
-class 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
781  
-application entirely), or want each model to have its own database
782  
-table, :ref:`multi-table-inheritance` is the way to go.
  776
+There are three styles of inheritance that are possible in Django.
  777
+
  778
+ 1. Often, you will just want to use the parent class to hold information that
  779
+    you don't want to have to type out for each child model. This class isn't
  780
+    going to ever be used in isolation, so :ref:`abstract-base-classes` are
  781
+    what you're after.
  782
+ 2. If you're subclassing an existing model (perhaps something from another
  783
+    application entirely) and want each model to have its own database table,
  784
+    :ref:`multi-table-inheritance` is the way to go.
  785
+ 3. Finally, if you only want to modify the Python-level behaviour of a model,
  786
+    without changing the models fields in any way, you can use
  787
+    :ref:`proxy-models`.
783 788
 
784 789
 .. _abstract-base-classes:
785 790
 
@@ -937,14 +942,16 @@ referring to ``p.restaurant`` would raise a Restaurant.DoesNotExist exception.
937 942
 In the multi-table inheritance situation, it doesn't make sense for a child
938 943
 class to inherit from its parent's :ref:`Meta <meta-options>` class. All the :ref:`Meta <meta-options>` options
939 944
 have already been applied to the parent class and applying them again would
940  
-normally only lead to contradictory behaviour (this is in contrast with the
  945
+normally only lead to contradictory behavior (this is in contrast with the
941 946
 abstract base class case, where the base class doesn't exist in its own
942 947
 right).
943 948
 
944  
-So a child model does not have access to its parent's :ref:`Meta <meta-options>` class. However,
945  
-there are a few limited cases where the child inherits behaviour from the
946  
-parent: if the child does not specify an :attr:`django.db.models.Options.ordering` attribute or a
947  
-:attr:`django.db.models.Options.get_latest_by` attribute, it will inherit these from its parent.
  949
+So a child model does not have access to its parent's :ref:`Meta
  950
+<meta-options>` class. However, there are a few limited cases where the child
  951
+inherits behavior from the parent: if the child does not specify an
  952
+:attr:`django.db.models.Options.ordering` attribute or a
  953
+:attr:`django.db.models.Options.get_latest_by` attribute, it will inherit
  954
+these from its parent.
948 955
 
949 956
 If the parent has an ordering and you don't want the child to have any natural
950 957
 ordering, you can explicitly disable it::
@@ -990,6 +997,126 @@ own :class:`~django.db.models.fields.OneToOneField` and set
990 997
 :attr:`parent_link=True <django.db.models.fields.OneToOneField.parent_link>`
991 998
 to indicate that your field is the link back to the parent class.
992 999
 
  1000
+.. _proxy-models:
  1001
+
  1002
+Proxy models
  1003
+------------
  1004
+
  1005
+.. versionadded:: 1.1
  1006
+
  1007
+When using :ref:`multi-table inheritance <multi-table-inheritance>`, a new
  1008
+database table is created for each subclass of a model. This is usually the
  1009
+desired behavior, since the subclass needs a place to store any additional
  1010
+data fields that are not present on the base class. Sometimes, however, you
  1011
+only want to change the Python behavior of a model -- perhaps to change the
  1012
+default manager, or add a new method.
  1013
+
  1014
+This is what proxy model inheritance is for: creating a *proxy* for the
  1015
+original model. You can create, delete and update instances of the proxy model
  1016
+and all the data will be saved as if you were using the original (non-proxied)
  1017
+model. The difference is that you can change things like the default model
  1018
+ordering or the default manager in the proxy, without having to alter the
  1019
+original.
  1020
+
  1021
+Proxy models are declared like normal models. You tell Django that it's a
  1022
+proxy model by setting the :attr:`~django.db.models.Options.proxy` attribute to of the ``Meta`` class to ``True``.
  1023
+
  1024
+For example, suppose you want to add a method to the standard ``User`` model
  1025
+that will make be used in your templates. You can do it like this::
  1026
+
  1027
+    from django.contrib.auth.models import User
  1028
+
  1029
+    class MyUser(User):
  1030
+        class Meta:
  1031
+            proxy = True
  1032
+
  1033
+        def do_something(self):
  1034
+            ...
  1035
+
  1036
+The ``MyUser`` class operates on the same database table as its parent
  1037
+``User`` class. In particular, any new instances of ``User`` will also be
  1038
+accessible through ``MyUser``, and vice-versa::
  1039
+
  1040
+    >>> u = User.objects.create(username="foobar")
  1041
+    >>> MyUser.objects.get(username="foobar")
  1042
+    <MyUser: foobar>
  1043
+
  1044
+You could also use a proxy model to define a different default ordering on a
  1045
+model. The standard ``User`` model has no ordering defined on it
  1046
+(intentionally; sorting is expensive and we don't want to do it all the time
  1047
+when we fetch users). You might want to regularly order by the ``username``
  1048
+attribute when you use the proxy. This is easy::
  1049
+
  1050
+    class OrderedUser(User):
  1051
+        class Meta:
  1052
+            ordering = ["username"]
  1053
+            proxy = True
  1054
+
  1055
+Now normal ``User`` queries will be unorderd and ``OrderedUser`` queries will
  1056
+be ordered by ``username``.
  1057
+
  1058
+Querysets still return the model that was requested
  1059
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1060
+
  1061
+There is no way to have Django return, say, a ``MyUser`` object whenever you
  1062
+query for ``User`` objects. A queryset for ``User`` objects will return those
  1063
+types of objects. The whole point of proxy objects is that code relying on the
  1064
+original ``User`` will use those and your own code can use the extensions you
  1065
+included (that no other code is relying on anyway). It is not a way to replace
  1066
+the ``User`` (or any other) model everywhere with something of your own
  1067
+creation.
  1068
+
  1069
+Base class restrictions
  1070
+~~~~~~~~~~~~~~~~~~~~~~~
  1071
+
  1072
+A proxy model must inherit from exactly one non-abstract model class. You
  1073
+can't inherit from multiple non-abstract models as the proxy model doesn't
  1074
+provide any connection between the rows in the different database tables. A
  1075
+proxy model can inherit from any number of abstract model classes, providing
  1076
+they do *not* define any model fields.
  1077
+
  1078
+Proxy models inherit any ``Meta`` options that they don't define from their
  1079
+non-abstract model parent (the model they are proxying for).
  1080
+
  1081
+Proxy model managers
  1082
+~~~~~~~~~~~~~~~~~~~~
  1083
+
  1084
+If you don't specify any model managers on a proxy model, it inherits the
  1085
+managers from its model parents. If you define a manager on the proxy model,
  1086
+it will become the default, although any managers defined on the parent
  1087
+classes will still be available.
  1088
+
  1089
+Continuing our example from above, you could change the default manager used
  1090
+when you query the ``User`` model like this::
  1091
+
  1092
+    class NewManager(models.Manager):
  1093
+        ...
  1094
+
  1095
+    class MyUser(User):
  1096
+        objects = NewManager()
  1097
+
  1098
+        class Meta:
  1099
+            proxy = True
  1100
+
  1101
+If you wanted to add a new manager to the Proxy, without replacing the
  1102
+existing default, you can use the techniques described in the :ref:`custom
  1103
+manager <custom-managers-and-inheritance>` documentation: create a base class
  1104
+containing the new managers and inherit that after the primary base class::
  1105
+
  1106
+    # Create an abstract class for the new manager.
  1107
+    class ExtraManagers:
  1108
+        secondary = NewManager()
  1109
+
  1110
+        class Meta:
  1111
+            abstract = True
  1112
+
  1113
+    class MyUser(User, ExtraManagers):
  1114
+        class Meta:
  1115
+            proxy = True
  1116
+
  1117
+You probably won't need to do this very often, but, when you do, it's
  1118
+possible.
  1119
+
993 1120
 Multiple inheritance
994 1121
 --------------------
995 1122
 
0  tests/modeltests/proxy_models/__init__.py
No changes.
176  tests/modeltests/proxy_models/models.py
... ...
@@ -0,0 +1,176 @@
  1
+"""
  2
+By specifying the 'proxy' Meta attribute, model subclasses can specify that
  3
+they will take data directly from the table of their base class table rather
  4
+than using a new table of their own. This allows them to act as simple proxies,
  5
+providing a modified interface to the data from the base class.
  6
+"""
  7
+
  8
+from django.db import models
  9
+
  10
+
  11
+# A couple of managers for testing managing overriding in proxy model cases.
  12
+
  13
+class PersonManager(models.Manager):
  14
+    def get_query_set(self):
  15
+        return super(PersonManager, self).get_query_set().exclude(name="fred")
  16
+
  17
+class SubManager(models.Manager):
  18
+    def get_query_set(self):
  19
+        return super(SubManager, self).get_query_set().exclude(name="wilma")
  20
+
  21
+class Person(models.Model):
  22
+    """
  23
+    A simple concrete base class.
  24
+    """
  25
+    name = models.CharField(max_length=50)
  26
+
  27
+    objects = PersonManager()
  28
+
  29
+    def __unicode__(self):
  30
+        return self.name
  31
+
  32
+class Abstract(models.Model):
  33
+    """
  34
+    A simple abstract base class, to be used for error checking.
  35
+    """
  36
+    data = models.CharField(max_length=10)
  37
+
  38
+    class Meta:
  39
+        abstract = True
  40
+
  41
+class MyPerson(Person):
  42
+    """
  43
+    A proxy subclass, this should not get a new table. Overrides the default
  44
+    manager.
  45
+    """
  46
+    class Meta:
  47
+        proxy = True
  48
+        ordering = ["name"]
  49
+
  50
+    objects = SubManager()
  51
+    other = PersonManager()
  52
+
  53
+    def has_special_name(self):
  54
+        return self.name.lower() == "special"
  55
+
  56
+class ManagerMixin(models.Model):
  57
+    excluder = SubManager()
  58
+
  59
+    class Meta:
  60
+        abstract = True
  61
+
  62
+class OtherPerson(Person, ManagerMixin):
  63
+    """
  64
+    A class with the default manager from Person, plus an secondary manager.
  65
+    """
  66
+    class Meta:
  67
+        proxy = True
  68
+        ordering = ["name"]
  69
+
  70
+class StatusPerson(MyPerson):
  71
+    """
  72
+    A non-proxy subclass of a proxy, it should get a new table.
  73
+    """
  74
+    status = models.CharField(max_length=80)
  75
+
  76
+# We can even have proxies of proxies (and subclass of those).
  77
+class MyPersonProxy(MyPerson):
  78
+    class Meta:
  79
+        proxy = True
  80
+
  81
+class LowerStatusPerson(MyPersonProxy):
  82
+    status = models.CharField(max_length=80)
  83
+
  84
+__test__ = {'API_TESTS' : """
  85
+# The MyPerson model should be generating the same database queries as the
  86
+# Person model (when the same manager is used in each case).
  87
+>>> MyPerson.other.all().query.as_sql() == Person.objects.order_by("name").query.as_sql()
  88
+True
  89
+
  90
+# The StatusPerson models should have its own table (it's using ORM-level
  91
+# inheritance).
  92
+>>> StatusPerson.objects.all().query.as_sql() == Person.objects.all().query.as_sql()
  93
+False
  94
+
  95
+# Creating a Person makes them accessible through the MyPerson proxy.
  96
+>>> _ = Person.objects.create(name="Foo McBar")
  97
+>>> len(Person.objects.all())
  98
+1
  99
+>>> len(MyPerson.objects.all())
  100
+1
  101
+>>> MyPerson.objects.get(name="Foo McBar").id
  102
+1
  103
+>>> MyPerson.objects.get(id=1).has_special_name()
  104
+False
  105
+
  106
+# Person is not proxied by StatusPerson subclass, however.
  107
+>>> StatusPerson.objects.all()
  108
+[]
  109
+
  110
+# A new MyPerson also shows up as a standard Person
  111
+>>> _ = MyPerson.objects.create(name="Bazza del Frob")
  112
+>>> len(MyPerson.objects.all())
  113
+2
  114
+>>> len(Person.objects.all())
  115
+2
  116
+
  117
+>>> _ = LowerStatusPerson.objects.create(status="low", name="homer")
  118
+>>> LowerStatusPerson.objects.all()
  119
+[<LowerStatusPerson: homer>]
  120
+
  121
+# And now for some things that shouldn't work...
  122
+#
  123
+# All base classes must be non-abstract
  124
+>>> class NoAbstract(Abstract):
  125
+...     class Meta:
  126
+...         proxy = True
  127
+Traceback (most recent call last):
  128
+    ....
  129
+TypeError: Abstract base class containing model fields not permitted for proxy model 'NoAbstract'.
  130
+
  131
+# The proxy must actually have one concrete base class
  132
+>>> class TooManyBases(Person, Abstract):
  133
+...     class Meta:
  134
+...         proxy = True
  135
+Traceback (most recent call last):
  136
+    ....
  137
+TypeError: Abstract base class containing model fields not permitted for proxy model 'TooManyBases'.
  138
+
  139
+>>> class NoBaseClasses(models.Model):
  140
+...     class Meta:
  141
+...         proxy = True
  142
+Traceback (most recent call last):
  143
+    ....
  144
+TypeError: Proxy model 'NoBaseClasses' has no non-abstract model base class.
  145
+
  146
+
  147
+# A proxy cannot introduce any new fields
  148
+>>> class NoNewFields(Person):
  149
+...     newfield = models.BooleanField()
  150
+...     class Meta:
  151
+...         proxy = True
  152
+Traceback (most recent call last):
  153
+    ....
  154
+FieldError: Proxy model 'NoNewFields' contains model fields.
  155
+
  156
+# Manager tests.
  157
+
  158
+>>> Person.objects.all().delete()
  159
+>>> _ = Person.objects.create(name="fred")
  160
+>>> _ = Person.objects.create(name="wilma")
  161
+>>> _ = Person.objects.create(name="barney")
  162
+
  163
+>>> MyPerson.objects.all()
  164
+[<MyPerson: barney>, <MyPerson: fred>]
  165
+>>> MyPerson._default_manager.all()
  166
+[<MyPerson: barney>, <MyPerson: fred>]
  167
+
  168
+>>> OtherPerson.objects.all()
  169
+[<OtherPerson: barney>, <OtherPerson: wilma>]
  170
+>>> OtherPerson.excluder.all()
  171
+[<OtherPerson: barney>, <OtherPerson: fred>]
  172
+>>> OtherPerson._default_manager.all()
  173
+[<OtherPerson: barney>, <OtherPerson: wilma>]
  174
+"""}
  175
+
  176
+

0 notes on commit 61a2708

Please sign in to comment.
Something went wrong with that request. Please try again.