summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2008-02-22 04:58:28 +0000
committerMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2008-02-22 04:58:28 +0000
commitb7be3d63e36ee79db51dcabf0f210349e4d6f715 (patch)
treef62cc873376eb4d6a060761fdc767cfe6a4809cf
parent0c20e88e65b8c2b1d097510ee2d7cfe6b2cf9b97 (diff)
queryset-refactor: Added the ability to manually specify a child-parent link.
git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7142 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/db/models/base.py48
-rw-r--r--django/db/models/fields/related.py14
-rw-r--r--django/db/models/options.py22
-rw-r--r--docs/model-api.txt37
-rw-r--r--tests/modeltests/model_inheritance/models.py2
5 files changed, 85 insertions, 38 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 9a4d6664ab..dc2b4ca0fe 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -59,21 +59,6 @@ class ModelBase(type):
if not hasattr(meta, 'get_latest_by'):
new_class._meta.get_latest_by = base_meta.get_latest_by
- # Do the appropriate setup for any model parents.
- abstract_parents = []
- for base in parents:
- if not hasattr(base, '_meta'):
- # Things without _meta aren't functional models, so they're
- # uninteresting parents.
- continue
- if not base._meta.abstract:
- attr_name = '%s_ptr' % base._meta.module_name
- field = OneToOneField(base, name=attr_name, auto_created=True)
- new_class.add_to_class(attr_name, field)
- new_class._meta.parents[base] = field
- else:
- abstract_parents.append(base)
-
if getattr(new_class, '_default_manager', None) is not None:
# We have a parent who set the default manager. We need to override
# this.
@@ -93,13 +78,32 @@ class ModelBase(type):
for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj)
- for parent in abstract_parents:
- names = [f.name for f in new_class._meta.local_fields + new_class._meta.many_to_many]
- for field in parent._meta.local_fields:
- if field.name in names:
- raise TypeError('Local field %r in class %r clashes with field of similar name from abstract base class %r'
- % (field.name, name, parent.__name__))
- new_class.add_to_class(field.name, field)
+ # Do the appropriate setup for any model parents.
+ o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
+ if isinstance(f, OneToOneField)])
+ for base in parents:
+ if not hasattr(base, '_meta'):
+ # Things without _meta aren't functional models, so they're
+ # uninteresting parents.
+ continue
+ if not base._meta.abstract:
+ if base in o2o_map:
+ field = o2o_map[base]
+ field.primary_key = True
+ new_class._meta.setup_pk(field)
+ else:
+ attr_name = '%s_ptr' % base._meta.module_name
+ field = OneToOneField(base, name=attr_name,
+ auto_created=True, parent_link=True)
+ new_class.add_to_class(attr_name, field)
+ new_class._meta.parents[base] = field
+ else:
+ names = [f.name for f in new_class._meta.local_fields + new_class._meta.many_to_many]
+ for field in base._meta.local_fields:
+ if field.name in names:
+ raise TypeError('Local field %r in class %r clashes with field of similar name from abstract base class %r'
+ % (field.name, name, base.__name__))
+ new_class.add_to_class(field.name, field)
if abstract:
# Abstract base models can't be instantiated and don't appear in
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 39fe6d794c..4ebf48beab 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -461,8 +461,9 @@ class ReverseManyRelatedObjectsDescriptor(object):
class ManyToOneRel(object):
def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
- max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
- related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
+ max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
+ related_name=None, limit_choices_to=None, lookup_overrides=None,
+ raw_id_admin=False, parent_link=False):
try:
to._meta
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
@@ -477,6 +478,7 @@ class ManyToOneRel(object):
self.lookup_overrides = lookup_overrides or {}
self.raw_id_admin = raw_id_admin
self.multiple = True
+ self.parent_link = parent_link
def get_related_field(self):
"""
@@ -489,14 +491,15 @@ class OneToOneRel(ManyToOneRel):
def __init__(self, to, field_name, num_in_admin=0, min_num_in_admin=None,
max_num_in_admin=None, num_extra_on_change=None, edit_inline=False,
related_name=None, limit_choices_to=None, lookup_overrides=None,
- raw_id_admin=False):
+ raw_id_admin=False, parent_link=False):
# NOTE: *_num_in_admin and num_extra_on_change are intentionally
# ignored here. We accept them as parameters only to match the calling
# signature of ManyToOneRel.__init__().
super(OneToOneRel, self).__init__(to, field_name, num_in_admin,
edit_inline=edit_inline, related_name=related_name,
limit_choices_to=limit_choices_to,
- lookup_overrides=lookup_overrides, raw_id_admin=raw_id_admin)
+ lookup_overrides=lookup_overrides, raw_id_admin=raw_id_admin,
+ parent_link=parent_link)
self.multiple = False
class ManyToManyRel(object):
@@ -541,7 +544,8 @@ class ForeignKey(RelatedField, Field):
related_name=kwargs.pop('related_name', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
lookup_overrides=kwargs.pop('lookup_overrides', None),
- raw_id_admin=kwargs.pop('raw_id_admin', False))
+ raw_id_admin=kwargs.pop('raw_id_admin', False),
+ parent_link=kwargs.pop('parent_link', False))
Field.__init__(self, **kwargs)
self.db_index = True
diff --git a/django/db/models/options.py b/django/db/models/options.py
index a5e853de33..7fa7fe0174 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -113,22 +113,22 @@ class Options(object):
# self.many_to_many.
if field.rel and isinstance(field.rel, ManyToManyRel):
self.local_many_to_many.insert(bisect(self.local_many_to_many, field), field)
+ if hasattr(self, '_m2m_cache'):
+ del self._m2m_cache
else:
self.local_fields.insert(bisect(self.local_fields, field), field)
- if not self.pk and field.primary_key:
- self.pk = field
- field.serialize = False
+ self.setup_pk(field)
+ if hasattr(self, '_field_cache'):
+ del self._field_cache
- # All of these internal caches need to be updated the next time they
- # are used.
- # TODO: Do this more neatly. (Also, use less caches!)
- if hasattr(self, '_field_cache'):
- del self._field_cache
- if hasattr(self, '_m2m_cache'):
- del self._m2m_cache
if hasattr(self, '_name_map'):
del self._name_map
+ def setup_pk(self, field):
+ if not self.pk and field.primary_key:
+ self.pk = field
+ field.serialize = False
+
def __repr__(self):
return '<Options for %s>' % self.object_name
@@ -315,7 +315,7 @@ class Options(object):
parent_list = self.get_parent_list()
for parent in self.parents:
for obj, model in parent._meta.get_all_related_objects_with_model():
- if obj.field.creation_counter < 0 and obj.model not in parent_list:
+ if (obj.field.creation_counter < 0 or obj.field.rel.parent_link) and obj.model not in parent_list:
continue
if not model:
cache[obj] = parent
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 2687c00d14..1926495728 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -1000,6 +1000,21 @@ As with ``ForeignKey``, a relationship to self can be defined by using the
string ``"self"`` instead of the model name; references to as-yet undefined
models can be made by using a string containing the model name.
+Finally, ``OneToOneField`` takes the following extra option:
+
+ ======================= ============================================================
+ Argument Description
+ ======================= ============================================================
+ ``parent_link`` When ``True`` and used in a model inherited from
+ another model, indicates that this field should
+ be used as the link from the child back to the
+ parent. See `Model inheritance`_ for more
+ details.
+
+ **New in Django development version**
+
+ ======================= ============================================================
+
**New in Django development version:** ``OneToOneField`` classes used to
automatically become the primary key on a model. This is no longer true,
although you can manually pass in the ``primary_key`` attribute if you like.
@@ -1036,6 +1051,14 @@ Model metadata is "anything that's not a field", such as ordering options, etc.
Here's a list of all possible ``Meta`` options. No options are required. Adding
``class Meta`` to a model is completely optional.
+``abstract``
+------------
+
+**New in Django development version**
+
+When set to ``True``, denotes this model as an abstract base class. See
+`Abstract base classes`_ for more details. Defaults to ``False``.
+
``db_table``
------------
@@ -2192,6 +2215,20 @@ For more information about reverse relations, refer to the `Database API
reference`_ . For now, just remember to run ``manage.py validate`` when
you're writing your models and pay attention to the error messages.
+Specifying the parent link field
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As mentioned, Django will automatically create a ``OneToOneField`` linking
+your child class back any non-abstract parent models. If you want to control
+the name of the attribute linking back to the parent, you can create your own
+link field and pass it ``parent_link=True``. For example, to explicitly
+specify the field that will link ``Supplier`` to ``Place`` in the above
+example, you could write::
+
+ class Supplier(Place):
+ parent = models.OneToOneField(Place, parent_link=True)
+ ...
+
Multiple inheritance
--------------------
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
index 1187c5fe64..df0fb77ccb 100644
--- a/tests/modeltests/model_inheritance/models.py
+++ b/tests/modeltests/model_inheritance/models.py
@@ -67,6 +67,8 @@ class Supplier(Place):
return u"%s the supplier" % self.name
class ParkingLot(Place):
+ # An explicit link to the parent (we can control the attribute name).
+ parent = models.OneToOneField(Place, primary_key=True, parent_link=True)
main_site = models.ForeignKey(Place, related_name='lot')
def __unicode__(self):