mirror of
https://github.com/django/django.git
synced 2025-07-06 10:49:17 +00:00
queryset-refactor: Fixed up OneToOneFields (mostly).
They now share as much code as possible with ForeignKeys, but behave more or less as they did before (the backwards incompatible change is that they are no longer automatically primary keys -- so more than one per model is permitted). The documentation still uses an example that is better suited to model inheritance, but that will change in due course. Also, the admin interface still shows them as read-only fields, which is probably wrong now, but that can change on newforms-admin after this branch is merged into trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@7096 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
55cd025670
commit
accc20d799
@ -459,13 +459,69 @@ class ReverseManyRelatedObjectsDescriptor(object):
|
|||||||
manager.clear()
|
manager.clear()
|
||||||
manager.add(*value)
|
manager.add(*value)
|
||||||
|
|
||||||
|
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):
|
||||||
|
try:
|
||||||
|
to._meta
|
||||||
|
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
|
||||||
|
assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
|
||||||
|
self.to, self.field_name = to, field_name
|
||||||
|
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
|
||||||
|
self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
|
||||||
|
self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
|
||||||
|
if limit_choices_to is None:
|
||||||
|
limit_choices_to = {}
|
||||||
|
self.limit_choices_to = limit_choices_to
|
||||||
|
self.lookup_overrides = lookup_overrides or {}
|
||||||
|
self.raw_id_admin = raw_id_admin
|
||||||
|
self.multiple = True
|
||||||
|
|
||||||
|
def get_related_field(self):
|
||||||
|
"""
|
||||||
|
Returns the Field in the 'to' object to which this relationship is
|
||||||
|
tied.
|
||||||
|
"""
|
||||||
|
return self.to._meta.get_field_by_name(self.field_name, True)[0]
|
||||||
|
|
||||||
|
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):
|
||||||
|
# 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, related_name, limit_choices_to, lookup_overrides,
|
||||||
|
raw_id_admin)
|
||||||
|
self.multiple = False
|
||||||
|
|
||||||
|
class ManyToManyRel(object):
|
||||||
|
def __init__(self, to, num_in_admin=0, related_name=None,
|
||||||
|
filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True):
|
||||||
|
self.to = to
|
||||||
|
self.num_in_admin = num_in_admin
|
||||||
|
self.related_name = related_name
|
||||||
|
self.filter_interface = filter_interface
|
||||||
|
if limit_choices_to is None:
|
||||||
|
limit_choices_to = {}
|
||||||
|
self.limit_choices_to = limit_choices_to
|
||||||
|
self.edit_inline = False
|
||||||
|
self.raw_id_admin = raw_id_admin
|
||||||
|
self.symmetrical = symmetrical
|
||||||
|
self.multiple = True
|
||||||
|
|
||||||
|
assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
|
||||||
|
|
||||||
class ForeignKey(RelatedField, Field):
|
class ForeignKey(RelatedField, Field):
|
||||||
empty_strings_allowed = False
|
empty_strings_allowed = False
|
||||||
def __init__(self, to, to_field=None, **kwargs):
|
def __init__(self, to, to_field=None, rel_class=ManyToOneRel, **kwargs):
|
||||||
try:
|
try:
|
||||||
to_name = to._meta.object_name.lower()
|
to_name = to._meta.object_name.lower()
|
||||||
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
|
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
|
||||||
assert isinstance(to, basestring), "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
|
assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
|
||||||
else:
|
else:
|
||||||
to_field = to_field or to._meta.pk.name
|
to_field = to_field or to._meta.pk.name
|
||||||
kwargs['verbose_name'] = kwargs.get('verbose_name', '')
|
kwargs['verbose_name'] = kwargs.get('verbose_name', '')
|
||||||
@ -475,7 +531,7 @@ class ForeignKey(RelatedField, Field):
|
|||||||
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
|
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
|
||||||
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
|
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
|
||||||
|
|
||||||
kwargs['rel'] = ManyToOneRel(to, to_field,
|
kwargs['rel'] = rel_class(to, to_field,
|
||||||
num_in_admin=kwargs.pop('num_in_admin', 3),
|
num_in_admin=kwargs.pop('num_in_admin', 3),
|
||||||
min_num_in_admin=kwargs.pop('min_num_in_admin', None),
|
min_num_in_admin=kwargs.pop('min_num_in_admin', None),
|
||||||
max_num_in_admin=kwargs.pop('max_num_in_admin', None),
|
max_num_in_admin=kwargs.pop('max_num_in_admin', None),
|
||||||
@ -563,82 +619,25 @@ class ForeignKey(RelatedField, Field):
|
|||||||
return IntegerField().db_type()
|
return IntegerField().db_type()
|
||||||
return rel_field.db_type()
|
return rel_field.db_type()
|
||||||
|
|
||||||
class OneToOneField(RelatedField, IntegerField):
|
class OneToOneField(ForeignKey):
|
||||||
|
"""
|
||||||
|
A OneToOneField is essentially the same as a ForeignKey, with the exception
|
||||||
|
that always carries a "unique" constraint with it and the reverse relation
|
||||||
|
always returns the object pointed to (since there will only ever be one),
|
||||||
|
rather than returning a list.
|
||||||
|
"""
|
||||||
def __init__(self, to, to_field=None, **kwargs):
|
def __init__(self, to, to_field=None, **kwargs):
|
||||||
try:
|
kwargs['unique'] = True
|
||||||
to_name = to._meta.object_name.lower()
|
if 'num_in_admin' not in kwargs:
|
||||||
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
|
kwargs['num_in_admin'] = 0
|
||||||
assert isinstance(to, basestring), "OneToOneField(%r) is invalid. First parameter to OneToOneField must be either a model, a model name, or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT)
|
super(OneToOneField, self).__init__(to, to_field, OneToOneRel, **kwargs)
|
||||||
else:
|
|
||||||
to_field = to_field or to._meta.pk.name
|
|
||||||
kwargs['verbose_name'] = kwargs.get('verbose_name', '')
|
|
||||||
|
|
||||||
if 'edit_inline_type' in kwargs:
|
|
||||||
import warnings
|
|
||||||
warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.")
|
|
||||||
kwargs['edit_inline'] = kwargs.pop('edit_inline_type')
|
|
||||||
|
|
||||||
kwargs['rel'] = OneToOneRel(to, to_field,
|
|
||||||
num_in_admin=kwargs.pop('num_in_admin', 0),
|
|
||||||
edit_inline=kwargs.pop('edit_inline', False),
|
|
||||||
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))
|
|
||||||
kwargs['primary_key'] = True
|
|
||||||
IntegerField.__init__(self, **kwargs)
|
|
||||||
|
|
||||||
self.db_index = True
|
|
||||||
|
|
||||||
def get_attname(self):
|
|
||||||
return '%s_id' % self.name
|
|
||||||
|
|
||||||
def get_validator_unique_lookup_type(self):
|
|
||||||
return '%s__%s__exact' % (self.name, self.rel.get_related_field().name)
|
|
||||||
|
|
||||||
# TODO: Copied from ForeignKey... putting this in RelatedField adversely affects
|
|
||||||
# ManyToManyField. This works for now.
|
|
||||||
def prepare_field_objs_and_params(self, manipulator, name_prefix):
|
|
||||||
params = {'validator_list': self.validator_list[:], 'member_name': name_prefix + self.attname}
|
|
||||||
if self.rel.raw_id_admin:
|
|
||||||
field_objs = self.get_manipulator_field_objs()
|
|
||||||
params['validator_list'].append(curry(manipulator_valid_rel_key, self, manipulator))
|
|
||||||
else:
|
|
||||||
if self.radio_admin:
|
|
||||||
field_objs = [oldforms.RadioSelectField]
|
|
||||||
params['ul_class'] = get_ul_class(self.radio_admin)
|
|
||||||
else:
|
|
||||||
if self.null:
|
|
||||||
field_objs = [oldforms.NullSelectField]
|
|
||||||
else:
|
|
||||||
field_objs = [oldforms.SelectField]
|
|
||||||
params['choices'] = self.get_choices_default()
|
|
||||||
return field_objs, params
|
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
|
||||||
super(OneToOneField, self).contribute_to_class(cls, name)
|
|
||||||
setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
|
|
||||||
|
|
||||||
def contribute_to_related_class(self, cls, related):
|
def contribute_to_related_class(self, cls, related):
|
||||||
setattr(cls, related.get_accessor_name(), SingleRelatedObjectDescriptor(related))
|
setattr(cls, related.get_accessor_name(),
|
||||||
|
SingleRelatedObjectDescriptor(related))
|
||||||
if not cls._meta.one_to_one_field:
|
if not cls._meta.one_to_one_field:
|
||||||
cls._meta.one_to_one_field = self
|
cls._meta.one_to_one_field = self
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
|
||||||
defaults = {'form_class': forms.ModelChoiceField, 'queryset': self.rel.to._default_manager.all()}
|
|
||||||
defaults.update(kwargs)
|
|
||||||
return super(OneToOneField, self).formfield(**defaults)
|
|
||||||
|
|
||||||
def db_type(self):
|
|
||||||
# The database column type of a OneToOneField is the column type
|
|
||||||
# of the field to which it points. An exception is if the OneToOneField
|
|
||||||
# points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField,
|
|
||||||
# in which case the column type is simply that of an IntegerField.
|
|
||||||
rel_field = self.rel.get_related_field()
|
|
||||||
if isinstance(rel_field, (AutoField, PositiveIntegerField, PositiveSmallIntegerField)):
|
|
||||||
return IntegerField().db_type()
|
|
||||||
return rel_field.db_type()
|
|
||||||
|
|
||||||
class ManyToManyField(RelatedField, Field):
|
class ManyToManyField(RelatedField, Field):
|
||||||
def __init__(self, to, **kwargs):
|
def __init__(self, to, **kwargs):
|
||||||
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
||||||
@ -770,59 +769,3 @@ class ManyToManyField(RelatedField, Field):
|
|||||||
# so return None.
|
# so return None.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
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):
|
|
||||||
try:
|
|
||||||
to._meta
|
|
||||||
except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
|
|
||||||
assert isinstance(to, basestring), "'to' must be either a model, a model name or the string %r" % RECURSIVE_RELATIONSHIP_CONSTANT
|
|
||||||
self.to, self.field_name = to, field_name
|
|
||||||
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
|
|
||||||
self.min_num_in_admin, self.max_num_in_admin = min_num_in_admin, max_num_in_admin
|
|
||||||
self.num_extra_on_change, self.related_name = num_extra_on_change, related_name
|
|
||||||
if limit_choices_to is None:
|
|
||||||
limit_choices_to = {}
|
|
||||||
self.limit_choices_to = limit_choices_to
|
|
||||||
self.lookup_overrides = lookup_overrides or {}
|
|
||||||
self.raw_id_admin = raw_id_admin
|
|
||||||
self.multiple = True
|
|
||||||
|
|
||||||
def get_related_field(self):
|
|
||||||
"""
|
|
||||||
Returns the Field in the 'to' object to which this relationship is
|
|
||||||
tied.
|
|
||||||
"""
|
|
||||||
return self.to._meta.get_field_by_name(self.field_name, True)[0]
|
|
||||||
|
|
||||||
class OneToOneRel(ManyToOneRel):
|
|
||||||
def __init__(self, to, field_name, num_in_admin=0, edit_inline=False,
|
|
||||||
related_name=None, limit_choices_to=None, lookup_overrides=None,
|
|
||||||
raw_id_admin=False):
|
|
||||||
self.to, self.field_name = to, field_name
|
|
||||||
self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
|
|
||||||
self.related_name = related_name
|
|
||||||
if limit_choices_to is None:
|
|
||||||
limit_choices_to = {}
|
|
||||||
self.limit_choices_to = limit_choices_to
|
|
||||||
self.lookup_overrides = lookup_overrides or {}
|
|
||||||
self.raw_id_admin = raw_id_admin
|
|
||||||
self.multiple = False
|
|
||||||
|
|
||||||
class ManyToManyRel(object):
|
|
||||||
def __init__(self, to, num_in_admin=0, related_name=None,
|
|
||||||
filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True):
|
|
||||||
self.to = to
|
|
||||||
self.num_in_admin = num_in_admin
|
|
||||||
self.related_name = related_name
|
|
||||||
self.filter_interface = filter_interface
|
|
||||||
if limit_choices_to is None:
|
|
||||||
limit_choices_to = {}
|
|
||||||
self.limit_choices_to = limit_choices_to
|
|
||||||
self.edit_inline = False
|
|
||||||
self.raw_id_admin = raw_id_admin
|
|
||||||
self.symmetrical = symmetrical
|
|
||||||
self.multiple = True
|
|
||||||
|
|
||||||
assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
|
|
||||||
|
@ -979,9 +979,6 @@ the relationship should work. All are optional:
|
|||||||
One-to-one relationships
|
One-to-one relationships
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The semantics of one-to-one relationships will be changing soon, so we don't
|
|
||||||
recommend you use them. If that doesn't scare you away, keep reading.
|
|
||||||
|
|
||||||
To define a one-to-one relationship, use ``OneToOneField``. You use it just
|
To define a one-to-one relationship, use ``OneToOneField``. You use it just
|
||||||
like any other ``Field`` type: by including it as a class attribute of your
|
like any other ``Field`` type: by including it as a class attribute of your
|
||||||
model.
|
model.
|
||||||
@ -1003,9 +1000,11 @@ 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
|
string ``"self"`` instead of the model name; references to as-yet undefined
|
||||||
models can be made by using a string containing the model name.
|
models can be made by using a string containing the model name.
|
||||||
|
|
||||||
This ``OneToOneField`` will actually replace the primary key ``id`` field
|
**New in Django development version:** ``OneToOneField`` classes used to
|
||||||
(since one-to-one relations share the same primary key), and will be displayed
|
automatically become the primary key on a model. This is no longer true,
|
||||||
as a read-only field when you edit an object in the admin interface:
|
although you can manually pass in the ``primary_key`` attribute if you like.
|
||||||
|
Thus, it's now possible to have multilpe fields of type ``OneToOneField`` on a
|
||||||
|
single model.
|
||||||
|
|
||||||
See the `One-to-one relationship model example`_ for a full example.
|
See the `One-to-one relationship model example`_ for a full example.
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class Place(models.Model):
|
|||||||
return u"%s the place" % self.name
|
return u"%s the place" % self.name
|
||||||
|
|
||||||
class Restaurant(models.Model):
|
class Restaurant(models.Model):
|
||||||
place = models.OneToOneField(Place)
|
place = models.OneToOneField(Place, primary_key=True)
|
||||||
serves_hot_dogs = models.BooleanField()
|
serves_hot_dogs = models.BooleanField()
|
||||||
serves_pizza = models.BooleanField()
|
serves_pizza = models.BooleanField()
|
||||||
|
|
||||||
@ -38,6 +38,14 @@ class RelatedModel(models.Model):
|
|||||||
link = models.OneToOneField(ManualPrimaryKey)
|
link = models.OneToOneField(ManualPrimaryKey)
|
||||||
name = models.CharField(max_length = 50)
|
name = models.CharField(max_length = 50)
|
||||||
|
|
||||||
|
class MultiModel(models.Model):
|
||||||
|
link1 = models.OneToOneField(Place)
|
||||||
|
link2 = models.OneToOneField(ManualPrimaryKey)
|
||||||
|
name = models.CharField(max_length=50)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u"Multimodel %s" % self.name
|
||||||
|
|
||||||
__test__ = {'API_TESTS':"""
|
__test__ = {'API_TESTS':"""
|
||||||
# Create a couple of Places.
|
# Create a couple of Places.
|
||||||
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
|
>>> p1 = Place(name='Demon Dogs', address='944 W. Fullerton')
|
||||||
@ -63,8 +71,8 @@ Traceback (most recent call last):
|
|||||||
...
|
...
|
||||||
DoesNotExist: Restaurant matching query does not exist.
|
DoesNotExist: Restaurant matching query does not exist.
|
||||||
|
|
||||||
# Set the place using assignment notation. Because place is the primary key on Restaurant,
|
# Set the place using assignment notation. Because place is the primary key on
|
||||||
# the save will create a new restaurant
|
# Restaurant, the save will create a new restaurant
|
||||||
>>> r.place = p2
|
>>> r.place = p2
|
||||||
>>> r.save()
|
>>> r.save()
|
||||||
>>> p2.restaurant
|
>>> p2.restaurant
|
||||||
@ -72,9 +80,9 @@ DoesNotExist: Restaurant matching query does not exist.
|
|||||||
>>> r.place
|
>>> r.place
|
||||||
<Place: Ace Hardware the place>
|
<Place: Ace Hardware the place>
|
||||||
|
|
||||||
# Set the place back again, using assignment in the reverse direction
|
# Set the place back again, using assignment in the reverse direction.
|
||||||
# Need to reget restaurant object first, because the reverse set
|
# Need to reget restaurant object first, because the reverse set can't update
|
||||||
# can't update the existing restaurant instance
|
# the existing restaurant instance
|
||||||
>>> p1.restaurant = r
|
>>> p1.restaurant = r
|
||||||
>>> r.save()
|
>>> r.save()
|
||||||
>>> p1.restaurant
|
>>> p1.restaurant
|
||||||
@ -86,8 +94,7 @@ DoesNotExist: Restaurant matching query does not exist.
|
|||||||
|
|
||||||
# Restaurant.objects.all() just returns the Restaurants, not the Places.
|
# Restaurant.objects.all() just returns the Restaurants, not the Places.
|
||||||
# Note that there are two restaurants - Ace Hardware the Restaurant was created
|
# Note that there are two restaurants - Ace Hardware the Restaurant was created
|
||||||
# in the call to r.place = p2. This means there are multiple restaurants referencing
|
# in the call to r.place = p2.
|
||||||
# a single place...
|
|
||||||
>>> Restaurant.objects.all()
|
>>> Restaurant.objects.all()
|
||||||
[<Restaurant: Demon Dogs the restaurant>, <Restaurant: Ace Hardware the restaurant>]
|
[<Restaurant: Demon Dogs the restaurant>, <Restaurant: Ace Hardware the restaurant>]
|
||||||
|
|
||||||
@ -165,4 +172,17 @@ DoesNotExist: Restaurant matching query does not exist.
|
|||||||
>>> o1.save()
|
>>> o1.save()
|
||||||
>>> o2 = RelatedModel(link=o1, name="secondary")
|
>>> o2 = RelatedModel(link=o1, name="secondary")
|
||||||
>>> o2.save()
|
>>> o2.save()
|
||||||
|
|
||||||
|
# You can have multiple one-to-one fields on a model, too.
|
||||||
|
>>> x1 = MultiModel(link1=p1, link2=o1, name="x1")
|
||||||
|
>>> x1.save()
|
||||||
|
>>> o1.multimodel
|
||||||
|
<MultiModel: Multimodel x1>
|
||||||
|
|
||||||
|
# This will fail because each one-to-one field must be unique (and link2=o1 was
|
||||||
|
# used for x1, above).
|
||||||
|
>>> MultiModel(link1=p2, link2=o1, name="x1").save()
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
IntegrityError: ...
|
||||||
"""}
|
"""}
|
||||||
|
@ -39,7 +39,7 @@ class Article(models.Model):
|
|||||||
return self.headline
|
return self.headline
|
||||||
|
|
||||||
class AuthorProfile(models.Model):
|
class AuthorProfile(models.Model):
|
||||||
author = models.OneToOneField(Author)
|
author = models.OneToOneField(Author, primary_key=True)
|
||||||
date_of_birth = models.DateField()
|
date_of_birth = models.DateField()
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
|
@ -117,8 +117,8 @@ class M2MData(models.Model):
|
|||||||
data = models.ManyToManyField(Anchor, null=True)
|
data = models.ManyToManyField(Anchor, null=True)
|
||||||
|
|
||||||
class O2OData(models.Model):
|
class O2OData(models.Model):
|
||||||
# One to one field can't be null, since it is a PK.
|
# One to one field can't be null here, since it is a PK.
|
||||||
data = models.OneToOneField(Anchor)
|
data = models.OneToOneField(Anchor, primary_key=True)
|
||||||
|
|
||||||
class FKSelfData(models.Model):
|
class FKSelfData(models.Model):
|
||||||
data = models.ForeignKey('self', null=True)
|
data = models.ForeignKey('self', null=True)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user