Fixed #19774 -- Deprecated the contenttypes.generic module.
It contained models, forms and admin objects causing undesirable import side effects. Refs #16368. Thanks to Ramiro, Carl and Loïc for the review.
This commit is contained in:
parent
c3881944e8
commit
10e3faf191
|
@ -1,6 +1,6 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.comments.managers import CommentManager
|
from django.contrib.comments.managers import CommentManager
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.core import urlresolvers
|
from django.core import urlresolvers
|
||||||
|
@ -23,7 +23,7 @@ class BaseCommentAbstractModel(models.Model):
|
||||||
verbose_name=_('content type'),
|
verbose_name=_('content type'),
|
||||||
related_name="content_type_set_for_%(class)s")
|
related_name="content_type_set_for_%(class)s")
|
||||||
object_pk = models.TextField(_('object ID'))
|
object_pk = models.TextField(_('object ID'))
|
||||||
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
|
content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")
|
||||||
|
|
||||||
# Metadata about the comment
|
# Metadata about the comment
|
||||||
site = models.ForeignKey(Site)
|
site = models.ForeignKey(Site)
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
|
||||||
|
from django.contrib.contenttypes.forms import (
|
||||||
|
BaseGenericInlineFormSet, generic_inlineformset_factory
|
||||||
|
)
|
||||||
|
from django.forms import ALL_FIELDS
|
||||||
|
from django.forms.models import modelform_defines_fields
|
||||||
|
|
||||||
|
|
||||||
|
class GenericInlineModelAdmin(InlineModelAdmin):
|
||||||
|
ct_field = "content_type"
|
||||||
|
ct_fk_field = "object_id"
|
||||||
|
formset = BaseGenericInlineFormSet
|
||||||
|
|
||||||
|
def get_formset(self, request, obj=None, **kwargs):
|
||||||
|
if 'fields' in kwargs:
|
||||||
|
fields = kwargs.pop('fields')
|
||||||
|
else:
|
||||||
|
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
|
||||||
|
if self.exclude is None:
|
||||||
|
exclude = []
|
||||||
|
else:
|
||||||
|
exclude = list(self.exclude)
|
||||||
|
exclude.extend(self.get_readonly_fields(request, obj))
|
||||||
|
if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
|
||||||
|
# Take the custom ModelForm's Meta.exclude into account only if the
|
||||||
|
# GenericInlineModelAdmin doesn't define its own.
|
||||||
|
exclude.extend(self.form._meta.exclude)
|
||||||
|
exclude = exclude or None
|
||||||
|
can_delete = self.can_delete and self.has_delete_permission(request, obj)
|
||||||
|
defaults = {
|
||||||
|
"ct_field": self.ct_field,
|
||||||
|
"fk_field": self.ct_fk_field,
|
||||||
|
"form": self.form,
|
||||||
|
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
|
||||||
|
"formset": self.formset,
|
||||||
|
"extra": self.extra,
|
||||||
|
"can_delete": can_delete,
|
||||||
|
"can_order": False,
|
||||||
|
"fields": fields,
|
||||||
|
"max_num": self.max_num,
|
||||||
|
"exclude": exclude
|
||||||
|
}
|
||||||
|
defaults.update(kwargs)
|
||||||
|
|
||||||
|
if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
|
||||||
|
defaults['fields'] = ALL_FIELDS
|
||||||
|
|
||||||
|
return generic_inlineformset_factory(self.model, **defaults)
|
||||||
|
|
||||||
|
|
||||||
|
class GenericStackedInline(GenericInlineModelAdmin):
|
||||||
|
template = 'admin/edit_inline/stacked.html'
|
||||||
|
|
||||||
|
|
||||||
|
class GenericTabularInline(GenericInlineModelAdmin):
|
||||||
|
template = 'admin/edit_inline/tabular.html'
|
|
@ -6,7 +6,7 @@ from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
def check_generic_foreign_keys(**kwargs):
|
def check_generic_foreign_keys(**kwargs):
|
||||||
from .generic import GenericForeignKey
|
from .fields import GenericForeignKey
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
fields = (obj
|
fields = (obj
|
||||||
|
|
|
@ -0,0 +1,576 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from django.core import checks
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.db import connection
|
||||||
|
from django.db import models, router, transaction, DEFAULT_DB_ALIAS
|
||||||
|
from django.db.models import signals, FieldDoesNotExist
|
||||||
|
from django.db.models.base import ModelBase
|
||||||
|
from django.db.models.fields.related import ForeignObject, ForeignObjectRel
|
||||||
|
from django.db.models.related import PathInfo
|
||||||
|
from django.db.models.sql.datastructures import Col
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.utils import six
|
||||||
|
from django.utils.deprecation import RenameMethodsBase
|
||||||
|
from django.utils.encoding import smart_text, python_2_unicode_compatible
|
||||||
|
|
||||||
|
|
||||||
|
class RenameGenericForeignKeyMethods(RenameMethodsBase):
|
||||||
|
renamed_methods = (
|
||||||
|
('get_prefetch_query_set', 'get_prefetch_queryset', DeprecationWarning),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class GenericForeignKey(six.with_metaclass(RenameGenericForeignKeyMethods)):
|
||||||
|
"""
|
||||||
|
Provides a generic relation to any object through content-type/object-id
|
||||||
|
fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, ct_field="content_type", fk_field="object_id", for_concrete_model=True):
|
||||||
|
self.ct_field = ct_field
|
||||||
|
self.fk_field = fk_field
|
||||||
|
self.for_concrete_model = for_concrete_model
|
||||||
|
self.editable = False
|
||||||
|
|
||||||
|
def contribute_to_class(self, cls, name):
|
||||||
|
self.name = name
|
||||||
|
self.model = cls
|
||||||
|
self.cache_attr = "_%s_cache" % name
|
||||||
|
cls._meta.add_virtual_field(self)
|
||||||
|
|
||||||
|
# Only run pre-initialization field assignment on non-abstract models
|
||||||
|
if not cls._meta.abstract:
|
||||||
|
signals.pre_init.connect(self.instance_pre_init, sender=cls)
|
||||||
|
|
||||||
|
setattr(cls, name, self)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
model = self.model
|
||||||
|
app = model._meta.app_label
|
||||||
|
return '%s.%s.%s' % (app, model._meta.object_name, self.name)
|
||||||
|
|
||||||
|
def check(self, **kwargs):
|
||||||
|
errors = []
|
||||||
|
errors.extend(self._check_content_type_field())
|
||||||
|
errors.extend(self._check_object_id_field())
|
||||||
|
errors.extend(self._check_field_name())
|
||||||
|
return errors
|
||||||
|
|
||||||
|
def _check_content_type_field(self):
|
||||||
|
return _check_content_type_field(
|
||||||
|
model=self.model,
|
||||||
|
field_name=self.ct_field,
|
||||||
|
checked_object=self)
|
||||||
|
|
||||||
|
def _check_object_id_field(self):
|
||||||
|
try:
|
||||||
|
self.model._meta.get_field(self.fk_field)
|
||||||
|
except FieldDoesNotExist:
|
||||||
|
return [
|
||||||
|
checks.Error(
|
||||||
|
'The field refers to "%s" field which is missing.' % self.fk_field,
|
||||||
|
hint=None,
|
||||||
|
obj=self,
|
||||||
|
id='contenttypes.E001',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _check_field_name(self):
|
||||||
|
if self.name.endswith("_"):
|
||||||
|
return [
|
||||||
|
checks.Error(
|
||||||
|
'Field names must not end with underscores.',
|
||||||
|
hint=None,
|
||||||
|
obj=self,
|
||||||
|
id='contenttypes.E002',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
|
||||||
|
"""
|
||||||
|
Handles initializing an object with the generic FK instead of
|
||||||
|
content-type/object-id fields.
|
||||||
|
"""
|
||||||
|
if self.name in kwargs:
|
||||||
|
value = kwargs.pop(self.name)
|
||||||
|
if value is not None:
|
||||||
|
kwargs[self.ct_field] = self.get_content_type(obj=value)
|
||||||
|
kwargs[self.fk_field] = value._get_pk_val()
|
||||||
|
else:
|
||||||
|
kwargs[self.ct_field] = None
|
||||||
|
kwargs[self.fk_field] = None
|
||||||
|
|
||||||
|
def get_content_type(self, obj=None, id=None, using=None):
|
||||||
|
if obj is not None:
|
||||||
|
return ContentType.objects.db_manager(obj._state.db).get_for_model(
|
||||||
|
obj, for_concrete_model=self.for_concrete_model)
|
||||||
|
elif id is not None:
|
||||||
|
return ContentType.objects.db_manager(using).get_for_id(id)
|
||||||
|
else:
|
||||||
|
# This should never happen. I love comments like this, don't you?
|
||||||
|
raise Exception("Impossible arguments to GFK.get_content_type!")
|
||||||
|
|
||||||
|
def get_prefetch_queryset(self, instances, queryset=None):
|
||||||
|
if queryset is not None:
|
||||||
|
raise ValueError("Custom queryset can't be used for this lookup.")
|
||||||
|
|
||||||
|
# For efficiency, group the instances by content type and then do one
|
||||||
|
# query per model
|
||||||
|
fk_dict = defaultdict(set)
|
||||||
|
# We need one instance for each group in order to get the right db:
|
||||||
|
instance_dict = {}
|
||||||
|
ct_attname = self.model._meta.get_field(self.ct_field).get_attname()
|
||||||
|
for instance in instances:
|
||||||
|
# We avoid looking for values if either ct_id or fkey value is None
|
||||||
|
ct_id = getattr(instance, ct_attname)
|
||||||
|
if ct_id is not None:
|
||||||
|
fk_val = getattr(instance, self.fk_field)
|
||||||
|
if fk_val is not None:
|
||||||
|
fk_dict[ct_id].add(fk_val)
|
||||||
|
instance_dict[ct_id] = instance
|
||||||
|
|
||||||
|
ret_val = []
|
||||||
|
for ct_id, fkeys in fk_dict.items():
|
||||||
|
instance = instance_dict[ct_id]
|
||||||
|
ct = self.get_content_type(id=ct_id, using=instance._state.db)
|
||||||
|
ret_val.extend(ct.get_all_objects_for_this_type(pk__in=fkeys))
|
||||||
|
|
||||||
|
# For doing the join in Python, we have to match both the FK val and the
|
||||||
|
# content type, so we use a callable that returns a (fk, class) pair.
|
||||||
|
def gfk_key(obj):
|
||||||
|
ct_id = getattr(obj, ct_attname)
|
||||||
|
if ct_id is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
model = self.get_content_type(id=ct_id,
|
||||||
|
using=obj._state.db).model_class()
|
||||||
|
return (model._meta.pk.get_prep_value(getattr(obj, self.fk_field)),
|
||||||
|
model)
|
||||||
|
|
||||||
|
return (ret_val,
|
||||||
|
lambda obj: (obj._get_pk_val(), obj.__class__),
|
||||||
|
gfk_key,
|
||||||
|
True,
|
||||||
|
self.cache_attr)
|
||||||
|
|
||||||
|
def is_cached(self, instance):
|
||||||
|
return hasattr(instance, self.cache_attr)
|
||||||
|
|
||||||
|
def __get__(self, instance, instance_type=None):
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
|
||||||
|
try:
|
||||||
|
return getattr(instance, self.cache_attr)
|
||||||
|
except AttributeError:
|
||||||
|
rel_obj = None
|
||||||
|
|
||||||
|
# Make sure to use ContentType.objects.get_for_id() to ensure that
|
||||||
|
# lookups are cached (see ticket #5570). This takes more code than
|
||||||
|
# the naive ``getattr(instance, self.ct_field)``, but has better
|
||||||
|
# performance when dealing with GFKs in loops and such.
|
||||||
|
f = self.model._meta.get_field(self.ct_field)
|
||||||
|
ct_id = getattr(instance, f.get_attname(), None)
|
||||||
|
if ct_id is not None:
|
||||||
|
ct = self.get_content_type(id=ct_id, using=instance._state.db)
|
||||||
|
try:
|
||||||
|
rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field))
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
pass
|
||||||
|
setattr(instance, self.cache_attr, rel_obj)
|
||||||
|
return rel_obj
|
||||||
|
|
||||||
|
def __set__(self, instance, value):
|
||||||
|
ct = None
|
||||||
|
fk = None
|
||||||
|
if value is not None:
|
||||||
|
ct = self.get_content_type(obj=value)
|
||||||
|
fk = value._get_pk_val()
|
||||||
|
|
||||||
|
setattr(instance, self.ct_field, ct)
|
||||||
|
setattr(instance, self.fk_field, fk)
|
||||||
|
setattr(instance, self.cache_attr, value)
|
||||||
|
|
||||||
|
|
||||||
|
class GenericRelation(ForeignObject):
|
||||||
|
"""Provides an accessor to generic related objects (e.g. comments)"""
|
||||||
|
|
||||||
|
def __init__(self, to, **kwargs):
|
||||||
|
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
||||||
|
kwargs['rel'] = GenericRel(
|
||||||
|
self, to, related_name=kwargs.pop('related_name', None),
|
||||||
|
limit_choices_to=kwargs.pop('limit_choices_to', None),)
|
||||||
|
# Override content-type/object-id field names on the related class
|
||||||
|
self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
|
||||||
|
self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
|
||||||
|
|
||||||
|
self.for_concrete_model = kwargs.pop("for_concrete_model", True)
|
||||||
|
|
||||||
|
kwargs['blank'] = True
|
||||||
|
kwargs['editable'] = False
|
||||||
|
kwargs['serialize'] = False
|
||||||
|
# This construct is somewhat of an abuse of ForeignObject. This field
|
||||||
|
# represents a relation from pk to object_id field. But, this relation
|
||||||
|
# isn't direct, the join is generated reverse along foreign key. So,
|
||||||
|
# the from_field is object_id field, to_field is pk because of the
|
||||||
|
# reverse join.
|
||||||
|
super(GenericRelation, self).__init__(
|
||||||
|
to, to_fields=[],
|
||||||
|
from_fields=[self.object_id_field_name], **kwargs)
|
||||||
|
|
||||||
|
def check(self, **kwargs):
|
||||||
|
errors = super(GenericRelation, self).check(**kwargs)
|
||||||
|
errors.extend(self._check_content_type_field())
|
||||||
|
errors.extend(self._check_object_id_field())
|
||||||
|
errors.extend(self._check_generic_foreign_key_existence())
|
||||||
|
return errors
|
||||||
|
|
||||||
|
def _check_content_type_field(self):
|
||||||
|
target = self.rel.to
|
||||||
|
if isinstance(target, ModelBase):
|
||||||
|
return _check_content_type_field(
|
||||||
|
model=target,
|
||||||
|
field_name=self.content_type_field_name,
|
||||||
|
checked_object=self)
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _check_object_id_field(self):
|
||||||
|
target = self.rel.to
|
||||||
|
if isinstance(target, ModelBase):
|
||||||
|
opts = target._meta
|
||||||
|
try:
|
||||||
|
opts.get_field(self.object_id_field_name)
|
||||||
|
except FieldDoesNotExist:
|
||||||
|
return [
|
||||||
|
checks.Error(
|
||||||
|
'The field refers to %s.%s field which is missing.' % (
|
||||||
|
opts.object_name, self.object_id_field_name
|
||||||
|
),
|
||||||
|
hint=None,
|
||||||
|
obj=self,
|
||||||
|
id='contenttypes.E003',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _check_generic_foreign_key_existence(self):
|
||||||
|
target = self.rel.to
|
||||||
|
if isinstance(target, ModelBase):
|
||||||
|
# Using `vars` is very ugly approach, but there is no better one,
|
||||||
|
# because GenericForeignKeys are not considered as fields and,
|
||||||
|
# therefore, are not included in `target._meta.local_fields`.
|
||||||
|
fields = target._meta.virtual_fields
|
||||||
|
if any(isinstance(field, GenericForeignKey) and
|
||||||
|
field.ct_field == self.content_type_field_name and
|
||||||
|
field.fk_field == self.object_id_field_name
|
||||||
|
for field in fields):
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return [
|
||||||
|
checks.Warning(
|
||||||
|
('The field defines a generic relation with the model '
|
||||||
|
'%s.%s, but the model lacks GenericForeignKey.') % (
|
||||||
|
target._meta.app_label, target._meta.object_name
|
||||||
|
),
|
||||||
|
hint=None,
|
||||||
|
obj=self,
|
||||||
|
id='contenttypes.E004',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def resolve_related_fields(self):
|
||||||
|
self.to_fields = [self.model._meta.pk.name]
|
||||||
|
return [(self.rel.to._meta.get_field_by_name(self.object_id_field_name)[0],
|
||||||
|
self.model._meta.pk)]
|
||||||
|
|
||||||
|
def get_reverse_path_info(self):
|
||||||
|
opts = self.rel.to._meta
|
||||||
|
target = opts.get_field_by_name(self.object_id_field_name)[0]
|
||||||
|
return [PathInfo(self.model._meta, opts, (target,), self.rel, True, False)]
|
||||||
|
|
||||||
|
def get_choices_default(self):
|
||||||
|
return super(GenericRelation, self).get_choices(include_blank=False)
|
||||||
|
|
||||||
|
def value_to_string(self, obj):
|
||||||
|
qs = getattr(obj, self.name).all()
|
||||||
|
return smart_text([instance._get_pk_val() for instance in qs])
|
||||||
|
|
||||||
|
def get_joining_columns(self, reverse_join=False):
|
||||||
|
if not reverse_join:
|
||||||
|
# This error message is meant for the user, and from user
|
||||||
|
# perspective this is a reverse join along the GenericRelation.
|
||||||
|
raise ValueError('Joining in reverse direction not allowed.')
|
||||||
|
return super(GenericRelation, self).get_joining_columns(reverse_join)
|
||||||
|
|
||||||
|
def contribute_to_class(self, cls, name):
|
||||||
|
super(GenericRelation, self).contribute_to_class(cls, name, virtual_only=True)
|
||||||
|
# Save a reference to which model this class is on for future use
|
||||||
|
self.model = cls
|
||||||
|
# Add the descriptor for the relation
|
||||||
|
setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self, self.for_concrete_model))
|
||||||
|
|
||||||
|
def contribute_to_related_class(self, cls, related):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_attributes_from_rel(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_internal_type(self):
|
||||||
|
return "ManyToManyField"
|
||||||
|
|
||||||
|
def get_content_type(self):
|
||||||
|
"""
|
||||||
|
Returns the content type associated with this field's model.
|
||||||
|
"""
|
||||||
|
return ContentType.objects.get_for_model(self.model,
|
||||||
|
for_concrete_model=self.for_concrete_model)
|
||||||
|
|
||||||
|
def get_extra_restriction(self, where_class, alias, remote_alias):
|
||||||
|
field = self.rel.to._meta.get_field_by_name(self.content_type_field_name)[0]
|
||||||
|
contenttype_pk = self.get_content_type().pk
|
||||||
|
cond = where_class()
|
||||||
|
lookup = field.get_lookup('exact')(Col(remote_alias, field, field), contenttype_pk)
|
||||||
|
cond.add(lookup, 'AND')
|
||||||
|
return cond
|
||||||
|
|
||||||
|
def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
|
||||||
|
"""
|
||||||
|
Return all objects related to ``objs`` via this ``GenericRelation``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.rel.to._base_manager.db_manager(using).filter(**{
|
||||||
|
"%s__pk" % self.content_type_field_name: ContentType.objects.db_manager(using).get_for_model(
|
||||||
|
self.model, for_concrete_model=self.for_concrete_model).pk,
|
||||||
|
"%s__in" % self.object_id_field_name: [obj.pk for obj in objs]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def _check_content_type_field(model, field_name, checked_object):
|
||||||
|
""" Check if field named `field_name` in model `model` exists and is
|
||||||
|
valid content_type field (is a ForeignKey to ContentType). """
|
||||||
|
|
||||||
|
try:
|
||||||
|
field = model._meta.get_field(field_name)
|
||||||
|
except FieldDoesNotExist:
|
||||||
|
return [
|
||||||
|
checks.Error(
|
||||||
|
'The field refers to %s.%s field which is missing.' % (
|
||||||
|
model._meta.object_name, field_name
|
||||||
|
),
|
||||||
|
hint=None,
|
||||||
|
obj=checked_object,
|
||||||
|
id='contenttypes.E005',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
if not isinstance(field, models.ForeignKey):
|
||||||
|
return [
|
||||||
|
checks.Error(
|
||||||
|
('"%s" field is used by a %s '
|
||||||
|
'as content type field and therefore it must be '
|
||||||
|
'a ForeignKey.') % (
|
||||||
|
field_name, checked_object.__class__.__name__
|
||||||
|
),
|
||||||
|
hint=None,
|
||||||
|
obj=checked_object,
|
||||||
|
id='contenttypes.E006',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
elif field.rel.to != ContentType:
|
||||||
|
return [
|
||||||
|
checks.Error(
|
||||||
|
('"%s" field is used by a %s '
|
||||||
|
'as content type field and therefore it must be '
|
||||||
|
'a ForeignKey to ContentType.') % (
|
||||||
|
field_name, checked_object.__class__.__name__
|
||||||
|
),
|
||||||
|
hint=None,
|
||||||
|
obj=checked_object,
|
||||||
|
id='contenttypes.E007',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
class ReverseGenericRelatedObjectsDescriptor(object):
|
||||||
|
"""
|
||||||
|
This class provides the functionality that makes the related-object
|
||||||
|
managers available as attributes on a model class, for fields that have
|
||||||
|
multiple "remote" values and have a GenericRelation defined in their model
|
||||||
|
(rather than having another model pointed *at* them). In the example
|
||||||
|
"article.publications", the publications attribute is a
|
||||||
|
ReverseGenericRelatedObjectsDescriptor instance.
|
||||||
|
"""
|
||||||
|
def __init__(self, field, for_concrete_model=True):
|
||||||
|
self.field = field
|
||||||
|
self.for_concrete_model = for_concrete_model
|
||||||
|
|
||||||
|
def __get__(self, instance, instance_type=None):
|
||||||
|
if instance is None:
|
||||||
|
return self
|
||||||
|
|
||||||
|
# Dynamically create a class that subclasses the related model's
|
||||||
|
# default manager.
|
||||||
|
rel_model = self.field.rel.to
|
||||||
|
superclass = rel_model._default_manager.__class__
|
||||||
|
RelatedManager = create_generic_related_manager(superclass)
|
||||||
|
|
||||||
|
qn = connection.ops.quote_name
|
||||||
|
content_type = ContentType.objects.db_manager(instance._state.db).get_for_model(
|
||||||
|
instance, for_concrete_model=self.for_concrete_model)
|
||||||
|
|
||||||
|
join_cols = self.field.get_joining_columns(reverse_join=True)[0]
|
||||||
|
manager = RelatedManager(
|
||||||
|
model=rel_model,
|
||||||
|
instance=instance,
|
||||||
|
source_col_name=qn(join_cols[0]),
|
||||||
|
target_col_name=qn(join_cols[1]),
|
||||||
|
content_type=content_type,
|
||||||
|
content_type_field_name=self.field.content_type_field_name,
|
||||||
|
object_id_field_name=self.field.object_id_field_name,
|
||||||
|
prefetch_cache_name=self.field.attname,
|
||||||
|
)
|
||||||
|
|
||||||
|
return manager
|
||||||
|
|
||||||
|
def __set__(self, instance, value):
|
||||||
|
manager = self.__get__(instance)
|
||||||
|
manager.clear()
|
||||||
|
for obj in value:
|
||||||
|
manager.add(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def create_generic_related_manager(superclass):
|
||||||
|
"""
|
||||||
|
Factory function for a manager that subclasses 'superclass' (which is a
|
||||||
|
Manager) and adds behavior for generic related objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class GenericRelatedObjectManager(superclass):
|
||||||
|
def __init__(self, model=None, instance=None, symmetrical=None,
|
||||||
|
source_col_name=None, target_col_name=None, content_type=None,
|
||||||
|
content_type_field_name=None, object_id_field_name=None,
|
||||||
|
prefetch_cache_name=None):
|
||||||
|
|
||||||
|
super(GenericRelatedObjectManager, self).__init__()
|
||||||
|
self.model = model
|
||||||
|
self.content_type = content_type
|
||||||
|
self.symmetrical = symmetrical
|
||||||
|
self.instance = instance
|
||||||
|
self.source_col_name = source_col_name
|
||||||
|
self.target_col_name = target_col_name
|
||||||
|
self.content_type_field_name = content_type_field_name
|
||||||
|
self.object_id_field_name = object_id_field_name
|
||||||
|
self.prefetch_cache_name = prefetch_cache_name
|
||||||
|
self.pk_val = self.instance._get_pk_val()
|
||||||
|
self.core_filters = {
|
||||||
|
'%s__pk' % content_type_field_name: content_type.id,
|
||||||
|
'%s' % object_id_field_name: instance._get_pk_val(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __call__(self, **kwargs):
|
||||||
|
# We use **kwargs rather than a kwarg argument to enforce the
|
||||||
|
# `manager='manager_name'` syntax.
|
||||||
|
manager = getattr(self.model, kwargs.pop('manager'))
|
||||||
|
manager_class = create_generic_related_manager(manager.__class__)
|
||||||
|
return manager_class(
|
||||||
|
model=self.model,
|
||||||
|
instance=self.instance,
|
||||||
|
symmetrical=self.symmetrical,
|
||||||
|
source_col_name=self.source_col_name,
|
||||||
|
target_col_name=self.target_col_name,
|
||||||
|
content_type=self.content_type,
|
||||||
|
content_type_field_name=self.content_type_field_name,
|
||||||
|
object_id_field_name=self.object_id_field_name,
|
||||||
|
prefetch_cache_name=self.prefetch_cache_name,
|
||||||
|
)
|
||||||
|
do_not_call_in_templates = True
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
try:
|
||||||
|
return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
db = self._db or router.db_for_read(self.model, instance=self.instance)
|
||||||
|
return super(GenericRelatedObjectManager, self).get_queryset().using(db).filter(**self.core_filters)
|
||||||
|
|
||||||
|
def get_prefetch_queryset(self, instances, queryset=None):
|
||||||
|
if queryset is None:
|
||||||
|
queryset = super(GenericRelatedObjectManager, self).get_queryset()
|
||||||
|
|
||||||
|
queryset._add_hints(instance=instances[0])
|
||||||
|
queryset = queryset.using(queryset._db or self._db)
|
||||||
|
|
||||||
|
query = {
|
||||||
|
'%s__pk' % self.content_type_field_name: self.content_type.id,
|
||||||
|
'%s__in' % self.object_id_field_name: set(obj._get_pk_val() for obj in instances)
|
||||||
|
}
|
||||||
|
|
||||||
|
# We (possibly) need to convert object IDs to the type of the
|
||||||
|
# instances' PK in order to match up instances:
|
||||||
|
object_id_converter = instances[0]._meta.pk.to_python
|
||||||
|
return (queryset.filter(**query),
|
||||||
|
lambda relobj: object_id_converter(getattr(relobj, self.object_id_field_name)),
|
||||||
|
lambda obj: obj._get_pk_val(),
|
||||||
|
False,
|
||||||
|
self.prefetch_cache_name)
|
||||||
|
|
||||||
|
def add(self, *objs):
|
||||||
|
for obj in objs:
|
||||||
|
if not isinstance(obj, self.model):
|
||||||
|
raise TypeError("'%s' instance expected" % self.model._meta.object_name)
|
||||||
|
setattr(obj, self.content_type_field_name, self.content_type)
|
||||||
|
setattr(obj, self.object_id_field_name, self.pk_val)
|
||||||
|
obj.save()
|
||||||
|
add.alters_data = True
|
||||||
|
|
||||||
|
def remove(self, *objs, **kwargs):
|
||||||
|
if not objs:
|
||||||
|
return
|
||||||
|
bulk = kwargs.pop('bulk', True)
|
||||||
|
self._clear(self.filter(pk__in=[o.pk for o in objs]), bulk)
|
||||||
|
remove.alters_data = True
|
||||||
|
|
||||||
|
def clear(self, **kwargs):
|
||||||
|
bulk = kwargs.pop('bulk', True)
|
||||||
|
self._clear(self, bulk)
|
||||||
|
clear.alters_data = True
|
||||||
|
|
||||||
|
def _clear(self, queryset, bulk):
|
||||||
|
db = router.db_for_write(self.model, instance=self.instance)
|
||||||
|
queryset = queryset.using(db)
|
||||||
|
if bulk:
|
||||||
|
queryset.delete()
|
||||||
|
else:
|
||||||
|
with transaction.commit_on_success_unless_managed(using=db, savepoint=False):
|
||||||
|
for obj in queryset:
|
||||||
|
obj.delete()
|
||||||
|
_clear.alters_data = True
|
||||||
|
|
||||||
|
def create(self, **kwargs):
|
||||||
|
kwargs[self.content_type_field_name] = self.content_type
|
||||||
|
kwargs[self.object_id_field_name] = self.pk_val
|
||||||
|
db = router.db_for_write(self.model, instance=self.instance)
|
||||||
|
return super(GenericRelatedObjectManager, self).using(db).create(**kwargs)
|
||||||
|
create.alters_data = True
|
||||||
|
|
||||||
|
return GenericRelatedObjectManager
|
||||||
|
|
||||||
|
|
||||||
|
class GenericRel(ForeignObjectRel):
|
||||||
|
def __init__(self, field, to, related_name=None, limit_choices_to=None):
|
||||||
|
super(GenericRel, self).__init__(field, to, related_name, limit_choices_to)
|
|
@ -0,0 +1,88 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.forms import ModelForm, modelformset_factory
|
||||||
|
from django.forms.models import BaseModelFormSet
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
|
||||||
|
class BaseGenericInlineFormSet(BaseModelFormSet):
|
||||||
|
"""
|
||||||
|
A formset for generic inline objects to a parent.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data=None, files=None, instance=None, save_as_new=None,
|
||||||
|
prefix=None, queryset=None, **kwargs):
|
||||||
|
opts = self.model._meta
|
||||||
|
self.instance = instance
|
||||||
|
self.rel_name = '-'.join((
|
||||||
|
opts.app_label, opts.model_name,
|
||||||
|
self.ct_field.name, self.ct_fk_field.name,
|
||||||
|
))
|
||||||
|
if self.instance is None or self.instance.pk is None:
|
||||||
|
qs = self.model._default_manager.none()
|
||||||
|
else:
|
||||||
|
if queryset is None:
|
||||||
|
queryset = self.model._default_manager
|
||||||
|
qs = queryset.filter(**{
|
||||||
|
self.ct_field.name: ContentType.objects.get_for_model(
|
||||||
|
self.instance, for_concrete_model=self.for_concrete_model),
|
||||||
|
self.ct_fk_field.name: self.instance.pk,
|
||||||
|
})
|
||||||
|
super(BaseGenericInlineFormSet, self).__init__(
|
||||||
|
queryset=qs, data=data, files=files,
|
||||||
|
prefix=prefix,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_default_prefix(cls):
|
||||||
|
opts = cls.model._meta
|
||||||
|
return '-'.join(
|
||||||
|
(opts.app_label, opts.model_name,
|
||||||
|
cls.ct_field.name, cls.ct_fk_field.name)
|
||||||
|
)
|
||||||
|
|
||||||
|
def save_new(self, form, commit=True):
|
||||||
|
setattr(form.instance, self.ct_field.get_attname(),
|
||||||
|
ContentType.objects.get_for_model(self.instance).pk)
|
||||||
|
setattr(form.instance, self.ct_fk_field.get_attname(),
|
||||||
|
self.instance.pk)
|
||||||
|
return form.save(commit=commit)
|
||||||
|
|
||||||
|
|
||||||
|
def generic_inlineformset_factory(model, form=ModelForm,
|
||||||
|
formset=BaseGenericInlineFormSet,
|
||||||
|
ct_field="content_type", fk_field="object_id",
|
||||||
|
fields=None, exclude=None,
|
||||||
|
extra=3, can_order=False, can_delete=True,
|
||||||
|
max_num=None,
|
||||||
|
formfield_callback=None, validate_max=False,
|
||||||
|
for_concrete_model=True):
|
||||||
|
"""
|
||||||
|
Returns a ``GenericInlineFormSet`` for the given kwargs.
|
||||||
|
|
||||||
|
You must provide ``ct_field`` and ``fk_field`` if they are different from
|
||||||
|
the defaults ``content_type`` and ``object_id`` respectively.
|
||||||
|
"""
|
||||||
|
opts = model._meta
|
||||||
|
# if there is no field called `ct_field` let the exception propagate
|
||||||
|
ct_field = opts.get_field(ct_field)
|
||||||
|
if not isinstance(ct_field, models.ForeignKey) or ct_field.rel.to != ContentType:
|
||||||
|
raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field)
|
||||||
|
fk_field = opts.get_field(fk_field) # let the exception propagate
|
||||||
|
if exclude is not None:
|
||||||
|
exclude = list(exclude)
|
||||||
|
exclude.extend([ct_field.name, fk_field.name])
|
||||||
|
else:
|
||||||
|
exclude = [ct_field.name, fk_field.name]
|
||||||
|
FormSet = modelformset_factory(model, form=form,
|
||||||
|
formfield_callback=formfield_callback,
|
||||||
|
formset=formset,
|
||||||
|
extra=extra, can_delete=can_delete, can_order=can_order,
|
||||||
|
fields=fields, exclude=exclude, max_num=max_num,
|
||||||
|
validate_max=validate_max)
|
||||||
|
FormSet.ct_field = ct_field
|
||||||
|
FormSet.ct_fk_field = fk_field
|
||||||
|
FormSet.for_concrete_model = for_concrete_model
|
||||||
|
return FormSet
|
|
@ -1,716 +1,20 @@
|
||||||
"""
|
|
||||||
Classes allowing "generic" relations through ContentType and object-id fields.
|
|
||||||
"""
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from collections import defaultdict
|
import warnings
|
||||||
from functools import partial
|
|
||||||
|
|
||||||
from django.core import checks
|
warnings.warn(
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
('django.contrib.contenttypes.generic is deprecated and will be removed in '
|
||||||
from django.db import connection
|
'Django 1.9. Its contents have been moved to the fields, forms, and admin '
|
||||||
from django.db import models, router, transaction, DEFAULT_DB_ALIAS
|
'submodules of django.contrib.contenttypes.'), PendingDeprecationWarning, stacklevel=2
|
||||||
from django.db.models import signals, FieldDoesNotExist
|
)
|
||||||
from django.db.models.base import ModelBase
|
|
||||||
from django.db.models.fields.related import ForeignObject, ForeignObjectRel
|
from django.contrib.contenttypes.admin import ( # NOQA
|
||||||
from django.db.models.related import PathInfo
|
GenericInlineModelAdmin, GenericStackedInline, GenericTabularInline
|
||||||
from django.db.models.sql.datastructures import Col
|
)
|
||||||
from django.forms import ModelForm, ALL_FIELDS
|
from django.contrib.contenttypes.fields import ( # NOQA
|
||||||
from django.forms.models import (BaseModelFormSet, modelformset_factory,
|
GenericForeignKey, GenericRelation
|
||||||
modelform_defines_fields)
|
)
|
||||||
from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
|
from django.contrib.contenttypes.forms import ( # NOQA
|
||||||
from django.contrib.contenttypes.models import ContentType
|
BaseGenericInlineFormSet, generic_inlineformset_factory
|
||||||
from django.utils import six
|
)
|
||||||
from django.utils.deprecation import RenameMethodsBase
|
|
||||||
from django.utils.encoding import smart_text, python_2_unicode_compatible
|
|
||||||
|
|
||||||
|
|
||||||
class RenameGenericForeignKeyMethods(RenameMethodsBase):
|
|
||||||
renamed_methods = (
|
|
||||||
('get_prefetch_query_set', 'get_prefetch_queryset', DeprecationWarning),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class GenericForeignKey(six.with_metaclass(RenameGenericForeignKeyMethods)):
|
|
||||||
"""
|
|
||||||
Provides a generic relation to any object through content-type/object-id
|
|
||||||
fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, ct_field="content_type", fk_field="object_id", for_concrete_model=True):
|
|
||||||
self.ct_field = ct_field
|
|
||||||
self.fk_field = fk_field
|
|
||||||
self.for_concrete_model = for_concrete_model
|
|
||||||
self.editable = False
|
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
|
||||||
self.name = name
|
|
||||||
self.model = cls
|
|
||||||
self.cache_attr = "_%s_cache" % name
|
|
||||||
cls._meta.add_virtual_field(self)
|
|
||||||
|
|
||||||
# Only run pre-initialization field assignment on non-abstract models
|
|
||||||
if not cls._meta.abstract:
|
|
||||||
signals.pre_init.connect(self.instance_pre_init, sender=cls)
|
|
||||||
|
|
||||||
setattr(cls, name, self)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
model = self.model
|
|
||||||
app = model._meta.app_label
|
|
||||||
return '%s.%s.%s' % (app, model._meta.object_name, self.name)
|
|
||||||
|
|
||||||
def check(self, **kwargs):
|
|
||||||
errors = []
|
|
||||||
errors.extend(self._check_content_type_field())
|
|
||||||
errors.extend(self._check_object_id_field())
|
|
||||||
errors.extend(self._check_field_name())
|
|
||||||
return errors
|
|
||||||
|
|
||||||
def _check_content_type_field(self):
|
|
||||||
return _check_content_type_field(
|
|
||||||
model=self.model,
|
|
||||||
field_name=self.ct_field,
|
|
||||||
checked_object=self)
|
|
||||||
|
|
||||||
def _check_object_id_field(self):
|
|
||||||
try:
|
|
||||||
self.model._meta.get_field(self.fk_field)
|
|
||||||
except FieldDoesNotExist:
|
|
||||||
return [
|
|
||||||
checks.Error(
|
|
||||||
'The field refers to "%s" field which is missing.' % self.fk_field,
|
|
||||||
hint=None,
|
|
||||||
obj=self,
|
|
||||||
id='contenttypes.E001',
|
|
||||||
)
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _check_field_name(self):
|
|
||||||
if self.name.endswith("_"):
|
|
||||||
return [
|
|
||||||
checks.Error(
|
|
||||||
'Field names must not end with underscores.',
|
|
||||||
hint=None,
|
|
||||||
obj=self,
|
|
||||||
id='contenttypes.E002',
|
|
||||||
)
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
|
|
||||||
"""
|
|
||||||
Handles initializing an object with the generic FK instead of
|
|
||||||
content-type/object-id fields.
|
|
||||||
"""
|
|
||||||
if self.name in kwargs:
|
|
||||||
value = kwargs.pop(self.name)
|
|
||||||
if value is not None:
|
|
||||||
kwargs[self.ct_field] = self.get_content_type(obj=value)
|
|
||||||
kwargs[self.fk_field] = value._get_pk_val()
|
|
||||||
else:
|
|
||||||
kwargs[self.ct_field] = None
|
|
||||||
kwargs[self.fk_field] = None
|
|
||||||
|
|
||||||
def get_content_type(self, obj=None, id=None, using=None):
|
|
||||||
if obj is not None:
|
|
||||||
return ContentType.objects.db_manager(obj._state.db).get_for_model(
|
|
||||||
obj, for_concrete_model=self.for_concrete_model)
|
|
||||||
elif id is not None:
|
|
||||||
return ContentType.objects.db_manager(using).get_for_id(id)
|
|
||||||
else:
|
|
||||||
# This should never happen. I love comments like this, don't you?
|
|
||||||
raise Exception("Impossible arguments to GFK.get_content_type!")
|
|
||||||
|
|
||||||
def get_prefetch_queryset(self, instances, queryset=None):
|
|
||||||
if queryset is not None:
|
|
||||||
raise ValueError("Custom queryset can't be used for this lookup.")
|
|
||||||
|
|
||||||
# For efficiency, group the instances by content type and then do one
|
|
||||||
# query per model
|
|
||||||
fk_dict = defaultdict(set)
|
|
||||||
# We need one instance for each group in order to get the right db:
|
|
||||||
instance_dict = {}
|
|
||||||
ct_attname = self.model._meta.get_field(self.ct_field).get_attname()
|
|
||||||
for instance in instances:
|
|
||||||
# We avoid looking for values if either ct_id or fkey value is None
|
|
||||||
ct_id = getattr(instance, ct_attname)
|
|
||||||
if ct_id is not None:
|
|
||||||
fk_val = getattr(instance, self.fk_field)
|
|
||||||
if fk_val is not None:
|
|
||||||
fk_dict[ct_id].add(fk_val)
|
|
||||||
instance_dict[ct_id] = instance
|
|
||||||
|
|
||||||
ret_val = []
|
|
||||||
for ct_id, fkeys in fk_dict.items():
|
|
||||||
instance = instance_dict[ct_id]
|
|
||||||
ct = self.get_content_type(id=ct_id, using=instance._state.db)
|
|
||||||
ret_val.extend(ct.get_all_objects_for_this_type(pk__in=fkeys))
|
|
||||||
|
|
||||||
# For doing the join in Python, we have to match both the FK val and the
|
|
||||||
# content type, so we use a callable that returns a (fk, class) pair.
|
|
||||||
def gfk_key(obj):
|
|
||||||
ct_id = getattr(obj, ct_attname)
|
|
||||||
if ct_id is None:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
model = self.get_content_type(id=ct_id,
|
|
||||||
using=obj._state.db).model_class()
|
|
||||||
return (model._meta.pk.get_prep_value(getattr(obj, self.fk_field)),
|
|
||||||
model)
|
|
||||||
|
|
||||||
return (ret_val,
|
|
||||||
lambda obj: (obj._get_pk_val(), obj.__class__),
|
|
||||||
gfk_key,
|
|
||||||
True,
|
|
||||||
self.cache_attr)
|
|
||||||
|
|
||||||
def is_cached(self, instance):
|
|
||||||
return hasattr(instance, self.cache_attr)
|
|
||||||
|
|
||||||
def __get__(self, instance, instance_type=None):
|
|
||||||
if instance is None:
|
|
||||||
return self
|
|
||||||
|
|
||||||
try:
|
|
||||||
return getattr(instance, self.cache_attr)
|
|
||||||
except AttributeError:
|
|
||||||
rel_obj = None
|
|
||||||
|
|
||||||
# Make sure to use ContentType.objects.get_for_id() to ensure that
|
|
||||||
# lookups are cached (see ticket #5570). This takes more code than
|
|
||||||
# the naive ``getattr(instance, self.ct_field)``, but has better
|
|
||||||
# performance when dealing with GFKs in loops and such.
|
|
||||||
f = self.model._meta.get_field(self.ct_field)
|
|
||||||
ct_id = getattr(instance, f.get_attname(), None)
|
|
||||||
if ct_id is not None:
|
|
||||||
ct = self.get_content_type(id=ct_id, using=instance._state.db)
|
|
||||||
try:
|
|
||||||
rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field))
|
|
||||||
except ObjectDoesNotExist:
|
|
||||||
pass
|
|
||||||
setattr(instance, self.cache_attr, rel_obj)
|
|
||||||
return rel_obj
|
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
|
||||||
ct = None
|
|
||||||
fk = None
|
|
||||||
if value is not None:
|
|
||||||
ct = self.get_content_type(obj=value)
|
|
||||||
fk = value._get_pk_val()
|
|
||||||
|
|
||||||
setattr(instance, self.ct_field, ct)
|
|
||||||
setattr(instance, self.fk_field, fk)
|
|
||||||
setattr(instance, self.cache_attr, value)
|
|
||||||
|
|
||||||
|
|
||||||
class GenericRelation(ForeignObject):
|
|
||||||
"""Provides an accessor to generic related objects (e.g. comments)"""
|
|
||||||
|
|
||||||
def __init__(self, to, **kwargs):
|
|
||||||
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
|
|
||||||
kwargs['rel'] = GenericRel(
|
|
||||||
self, to, related_name=kwargs.pop('related_name', None),
|
|
||||||
limit_choices_to=kwargs.pop('limit_choices_to', None),)
|
|
||||||
# Override content-type/object-id field names on the related class
|
|
||||||
self.object_id_field_name = kwargs.pop("object_id_field", "object_id")
|
|
||||||
self.content_type_field_name = kwargs.pop("content_type_field", "content_type")
|
|
||||||
|
|
||||||
self.for_concrete_model = kwargs.pop("for_concrete_model", True)
|
|
||||||
|
|
||||||
kwargs['blank'] = True
|
|
||||||
kwargs['editable'] = False
|
|
||||||
kwargs['serialize'] = False
|
|
||||||
# This construct is somewhat of an abuse of ForeignObject. This field
|
|
||||||
# represents a relation from pk to object_id field. But, this relation
|
|
||||||
# isn't direct, the join is generated reverse along foreign key. So,
|
|
||||||
# the from_field is object_id field, to_field is pk because of the
|
|
||||||
# reverse join.
|
|
||||||
super(GenericRelation, self).__init__(
|
|
||||||
to, to_fields=[],
|
|
||||||
from_fields=[self.object_id_field_name], **kwargs)
|
|
||||||
|
|
||||||
def check(self, **kwargs):
|
|
||||||
errors = super(GenericRelation, self).check(**kwargs)
|
|
||||||
errors.extend(self._check_content_type_field())
|
|
||||||
errors.extend(self._check_object_id_field())
|
|
||||||
errors.extend(self._check_generic_foreign_key_existence())
|
|
||||||
return errors
|
|
||||||
|
|
||||||
def _check_content_type_field(self):
|
|
||||||
target = self.rel.to
|
|
||||||
if isinstance(target, ModelBase):
|
|
||||||
return _check_content_type_field(
|
|
||||||
model=target,
|
|
||||||
field_name=self.content_type_field_name,
|
|
||||||
checked_object=self)
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _check_object_id_field(self):
|
|
||||||
target = self.rel.to
|
|
||||||
if isinstance(target, ModelBase):
|
|
||||||
opts = target._meta
|
|
||||||
try:
|
|
||||||
opts.get_field(self.object_id_field_name)
|
|
||||||
except FieldDoesNotExist:
|
|
||||||
return [
|
|
||||||
checks.Error(
|
|
||||||
'The field refers to %s.%s field which is missing.' % (
|
|
||||||
opts.object_name, self.object_id_field_name
|
|
||||||
),
|
|
||||||
hint=None,
|
|
||||||
obj=self,
|
|
||||||
id='contenttypes.E003',
|
|
||||||
)
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _check_generic_foreign_key_existence(self):
|
|
||||||
target = self.rel.to
|
|
||||||
if isinstance(target, ModelBase):
|
|
||||||
# Using `vars` is very ugly approach, but there is no better one,
|
|
||||||
# because GenericForeignKeys are not considered as fields and,
|
|
||||||
# therefore, are not included in `target._meta.local_fields`.
|
|
||||||
fields = target._meta.virtual_fields
|
|
||||||
if any(isinstance(field, GenericForeignKey) and
|
|
||||||
field.ct_field == self.content_type_field_name and
|
|
||||||
field.fk_field == self.object_id_field_name
|
|
||||||
for field in fields):
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return [
|
|
||||||
checks.Warning(
|
|
||||||
('The field defines a generic relation with the model '
|
|
||||||
'%s.%s, but the model lacks GenericForeignKey.') % (
|
|
||||||
target._meta.app_label, target._meta.object_name
|
|
||||||
),
|
|
||||||
hint=None,
|
|
||||||
obj=self,
|
|
||||||
id='contenttypes.E004',
|
|
||||||
)
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def resolve_related_fields(self):
|
|
||||||
self.to_fields = [self.model._meta.pk.name]
|
|
||||||
return [(self.rel.to._meta.get_field_by_name(self.object_id_field_name)[0],
|
|
||||||
self.model._meta.pk)]
|
|
||||||
|
|
||||||
def get_reverse_path_info(self):
|
|
||||||
opts = self.rel.to._meta
|
|
||||||
target = opts.get_field_by_name(self.object_id_field_name)[0]
|
|
||||||
return [PathInfo(self.model._meta, opts, (target,), self.rel, True, False)]
|
|
||||||
|
|
||||||
def get_choices_default(self):
|
|
||||||
return super(GenericRelation, self).get_choices(include_blank=False)
|
|
||||||
|
|
||||||
def value_to_string(self, obj):
|
|
||||||
qs = getattr(obj, self.name).all()
|
|
||||||
return smart_text([instance._get_pk_val() for instance in qs])
|
|
||||||
|
|
||||||
def get_joining_columns(self, reverse_join=False):
|
|
||||||
if not reverse_join:
|
|
||||||
# This error message is meant for the user, and from user
|
|
||||||
# perspective this is a reverse join along the GenericRelation.
|
|
||||||
raise ValueError('Joining in reverse direction not allowed.')
|
|
||||||
return super(GenericRelation, self).get_joining_columns(reverse_join)
|
|
||||||
|
|
||||||
def contribute_to_class(self, cls, name):
|
|
||||||
super(GenericRelation, self).contribute_to_class(cls, name, virtual_only=True)
|
|
||||||
# Save a reference to which model this class is on for future use
|
|
||||||
self.model = cls
|
|
||||||
# Add the descriptor for the relation
|
|
||||||
setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self, self.for_concrete_model))
|
|
||||||
|
|
||||||
def contribute_to_related_class(self, cls, related):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def set_attributes_from_rel(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_internal_type(self):
|
|
||||||
return "ManyToManyField"
|
|
||||||
|
|
||||||
def get_content_type(self):
|
|
||||||
"""
|
|
||||||
Returns the content type associated with this field's model.
|
|
||||||
"""
|
|
||||||
return ContentType.objects.get_for_model(self.model,
|
|
||||||
for_concrete_model=self.for_concrete_model)
|
|
||||||
|
|
||||||
def get_extra_restriction(self, where_class, alias, remote_alias):
|
|
||||||
field = self.rel.to._meta.get_field_by_name(self.content_type_field_name)[0]
|
|
||||||
contenttype_pk = self.get_content_type().pk
|
|
||||||
cond = where_class()
|
|
||||||
lookup = field.get_lookup('exact')(Col(remote_alias, field, field), contenttype_pk)
|
|
||||||
cond.add(lookup, 'AND')
|
|
||||||
return cond
|
|
||||||
|
|
||||||
def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
|
|
||||||
"""
|
|
||||||
Return all objects related to ``objs`` via this ``GenericRelation``.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self.rel.to._base_manager.db_manager(using).filter(**{
|
|
||||||
"%s__pk" % self.content_type_field_name: ContentType.objects.db_manager(using).get_for_model(
|
|
||||||
self.model, for_concrete_model=self.for_concrete_model).pk,
|
|
||||||
"%s__in" % self.object_id_field_name: [obj.pk for obj in objs]
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def _check_content_type_field(model, field_name, checked_object):
|
|
||||||
""" Check if field named `field_name` in model `model` exists and is
|
|
||||||
valid content_type field (is a ForeignKey to ContentType). """
|
|
||||||
|
|
||||||
try:
|
|
||||||
field = model._meta.get_field(field_name)
|
|
||||||
except FieldDoesNotExist:
|
|
||||||
return [
|
|
||||||
checks.Error(
|
|
||||||
'The field refers to %s.%s field which is missing.' % (
|
|
||||||
model._meta.object_name, field_name
|
|
||||||
),
|
|
||||||
hint=None,
|
|
||||||
obj=checked_object,
|
|
||||||
id='contenttypes.E005',
|
|
||||||
)
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
if not isinstance(field, models.ForeignKey):
|
|
||||||
return [
|
|
||||||
checks.Error(
|
|
||||||
('"%s" field is used by a %s '
|
|
||||||
'as content type field and therefore it must be '
|
|
||||||
'a ForeignKey.') % (
|
|
||||||
field_name, checked_object.__class__.__name__
|
|
||||||
),
|
|
||||||
hint=None,
|
|
||||||
obj=checked_object,
|
|
||||||
id='contenttypes.E006',
|
|
||||||
)
|
|
||||||
]
|
|
||||||
elif field.rel.to != ContentType:
|
|
||||||
return [
|
|
||||||
checks.Error(
|
|
||||||
('"%s" field is used by a %s '
|
|
||||||
'as content type field and therefore it must be '
|
|
||||||
'a ForeignKey to ContentType.') % (
|
|
||||||
field_name, checked_object.__class__.__name__
|
|
||||||
),
|
|
||||||
hint=None,
|
|
||||||
obj=checked_object,
|
|
||||||
id='contenttypes.E007',
|
|
||||||
)
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class ReverseGenericRelatedObjectsDescriptor(object):
|
|
||||||
"""
|
|
||||||
This class provides the functionality that makes the related-object
|
|
||||||
managers available as attributes on a model class, for fields that have
|
|
||||||
multiple "remote" values and have a GenericRelation defined in their model
|
|
||||||
(rather than having another model pointed *at* them). In the example
|
|
||||||
"article.publications", the publications attribute is a
|
|
||||||
ReverseGenericRelatedObjectsDescriptor instance.
|
|
||||||
"""
|
|
||||||
def __init__(self, field, for_concrete_model=True):
|
|
||||||
self.field = field
|
|
||||||
self.for_concrete_model = for_concrete_model
|
|
||||||
|
|
||||||
def __get__(self, instance, instance_type=None):
|
|
||||||
if instance is None:
|
|
||||||
return self
|
|
||||||
|
|
||||||
# Dynamically create a class that subclasses the related model's
|
|
||||||
# default manager.
|
|
||||||
rel_model = self.field.rel.to
|
|
||||||
superclass = rel_model._default_manager.__class__
|
|
||||||
RelatedManager = create_generic_related_manager(superclass)
|
|
||||||
|
|
||||||
qn = connection.ops.quote_name
|
|
||||||
content_type = ContentType.objects.db_manager(instance._state.db).get_for_model(
|
|
||||||
instance, for_concrete_model=self.for_concrete_model)
|
|
||||||
|
|
||||||
join_cols = self.field.get_joining_columns(reverse_join=True)[0]
|
|
||||||
manager = RelatedManager(
|
|
||||||
model=rel_model,
|
|
||||||
instance=instance,
|
|
||||||
source_col_name=qn(join_cols[0]),
|
|
||||||
target_col_name=qn(join_cols[1]),
|
|
||||||
content_type=content_type,
|
|
||||||
content_type_field_name=self.field.content_type_field_name,
|
|
||||||
object_id_field_name=self.field.object_id_field_name,
|
|
||||||
prefetch_cache_name=self.field.attname,
|
|
||||||
)
|
|
||||||
|
|
||||||
return manager
|
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
|
||||||
manager = self.__get__(instance)
|
|
||||||
manager.clear()
|
|
||||||
for obj in value:
|
|
||||||
manager.add(obj)
|
|
||||||
|
|
||||||
|
|
||||||
def create_generic_related_manager(superclass):
|
|
||||||
"""
|
|
||||||
Factory function for a manager that subclasses 'superclass' (which is a
|
|
||||||
Manager) and adds behavior for generic related objects.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class GenericRelatedObjectManager(superclass):
|
|
||||||
def __init__(self, model=None, instance=None, symmetrical=None,
|
|
||||||
source_col_name=None, target_col_name=None, content_type=None,
|
|
||||||
content_type_field_name=None, object_id_field_name=None,
|
|
||||||
prefetch_cache_name=None):
|
|
||||||
|
|
||||||
super(GenericRelatedObjectManager, self).__init__()
|
|
||||||
self.model = model
|
|
||||||
self.content_type = content_type
|
|
||||||
self.symmetrical = symmetrical
|
|
||||||
self.instance = instance
|
|
||||||
self.source_col_name = source_col_name
|
|
||||||
self.target_col_name = target_col_name
|
|
||||||
self.content_type_field_name = content_type_field_name
|
|
||||||
self.object_id_field_name = object_id_field_name
|
|
||||||
self.prefetch_cache_name = prefetch_cache_name
|
|
||||||
self.pk_val = self.instance._get_pk_val()
|
|
||||||
self.core_filters = {
|
|
||||||
'%s__pk' % content_type_field_name: content_type.id,
|
|
||||||
'%s' % object_id_field_name: instance._get_pk_val(),
|
|
||||||
}
|
|
||||||
|
|
||||||
def __call__(self, **kwargs):
|
|
||||||
# We use **kwargs rather than a kwarg argument to enforce the
|
|
||||||
# `manager='manager_name'` syntax.
|
|
||||||
manager = getattr(self.model, kwargs.pop('manager'))
|
|
||||||
manager_class = create_generic_related_manager(manager.__class__)
|
|
||||||
return manager_class(
|
|
||||||
model=self.model,
|
|
||||||
instance=self.instance,
|
|
||||||
symmetrical=self.symmetrical,
|
|
||||||
source_col_name=self.source_col_name,
|
|
||||||
target_col_name=self.target_col_name,
|
|
||||||
content_type=self.content_type,
|
|
||||||
content_type_field_name=self.content_type_field_name,
|
|
||||||
object_id_field_name=self.object_id_field_name,
|
|
||||||
prefetch_cache_name=self.prefetch_cache_name,
|
|
||||||
)
|
|
||||||
do_not_call_in_templates = True
|
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
try:
|
|
||||||
return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
|
|
||||||
except (AttributeError, KeyError):
|
|
||||||
db = self._db or router.db_for_read(self.model, instance=self.instance)
|
|
||||||
return super(GenericRelatedObjectManager, self).get_queryset().using(db).filter(**self.core_filters)
|
|
||||||
|
|
||||||
def get_prefetch_queryset(self, instances, queryset=None):
|
|
||||||
if queryset is None:
|
|
||||||
queryset = super(GenericRelatedObjectManager, self).get_queryset()
|
|
||||||
|
|
||||||
queryset._add_hints(instance=instances[0])
|
|
||||||
queryset = queryset.using(queryset._db or self._db)
|
|
||||||
|
|
||||||
query = {
|
|
||||||
'%s__pk' % self.content_type_field_name: self.content_type.id,
|
|
||||||
'%s__in' % self.object_id_field_name: set(obj._get_pk_val() for obj in instances)
|
|
||||||
}
|
|
||||||
|
|
||||||
# We (possibly) need to convert object IDs to the type of the
|
|
||||||
# instances' PK in order to match up instances:
|
|
||||||
object_id_converter = instances[0]._meta.pk.to_python
|
|
||||||
return (queryset.filter(**query),
|
|
||||||
lambda relobj: object_id_converter(getattr(relobj, self.object_id_field_name)),
|
|
||||||
lambda obj: obj._get_pk_val(),
|
|
||||||
False,
|
|
||||||
self.prefetch_cache_name)
|
|
||||||
|
|
||||||
def add(self, *objs):
|
|
||||||
for obj in objs:
|
|
||||||
if not isinstance(obj, self.model):
|
|
||||||
raise TypeError("'%s' instance expected" % self.model._meta.object_name)
|
|
||||||
setattr(obj, self.content_type_field_name, self.content_type)
|
|
||||||
setattr(obj, self.object_id_field_name, self.pk_val)
|
|
||||||
obj.save()
|
|
||||||
add.alters_data = True
|
|
||||||
|
|
||||||
def remove(self, *objs, **kwargs):
|
|
||||||
if not objs:
|
|
||||||
return
|
|
||||||
bulk = kwargs.pop('bulk', True)
|
|
||||||
self._clear(self.filter(pk__in=[o.pk for o in objs]), bulk)
|
|
||||||
remove.alters_data = True
|
|
||||||
|
|
||||||
def clear(self, **kwargs):
|
|
||||||
bulk = kwargs.pop('bulk', True)
|
|
||||||
self._clear(self, bulk)
|
|
||||||
clear.alters_data = True
|
|
||||||
|
|
||||||
def _clear(self, queryset, bulk):
|
|
||||||
db = router.db_for_write(self.model, instance=self.instance)
|
|
||||||
queryset = queryset.using(db)
|
|
||||||
if bulk:
|
|
||||||
queryset.delete()
|
|
||||||
else:
|
|
||||||
with transaction.commit_on_success_unless_managed(using=db, savepoint=False):
|
|
||||||
for obj in queryset:
|
|
||||||
obj.delete()
|
|
||||||
_clear.alters_data = True
|
|
||||||
|
|
||||||
def create(self, **kwargs):
|
|
||||||
kwargs[self.content_type_field_name] = self.content_type
|
|
||||||
kwargs[self.object_id_field_name] = self.pk_val
|
|
||||||
db = router.db_for_write(self.model, instance=self.instance)
|
|
||||||
return super(GenericRelatedObjectManager, self).using(db).create(**kwargs)
|
|
||||||
create.alters_data = True
|
|
||||||
|
|
||||||
return GenericRelatedObjectManager
|
|
||||||
|
|
||||||
|
|
||||||
class GenericRel(ForeignObjectRel):
|
|
||||||
def __init__(self, field, to, related_name=None, limit_choices_to=None):
|
|
||||||
super(GenericRel, self).__init__(field, to, related_name, limit_choices_to)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseGenericInlineFormSet(BaseModelFormSet):
|
|
||||||
"""
|
|
||||||
A formset for generic inline objects to a parent.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, data=None, files=None, instance=None, save_as_new=None,
|
|
||||||
prefix=None, queryset=None, **kwargs):
|
|
||||||
opts = self.model._meta
|
|
||||||
self.instance = instance
|
|
||||||
self.rel_name = '-'.join((
|
|
||||||
opts.app_label, opts.model_name,
|
|
||||||
self.ct_field.name, self.ct_fk_field.name,
|
|
||||||
))
|
|
||||||
if self.instance is None or self.instance.pk is None:
|
|
||||||
qs = self.model._default_manager.none()
|
|
||||||
else:
|
|
||||||
if queryset is None:
|
|
||||||
queryset = self.model._default_manager
|
|
||||||
qs = queryset.filter(**{
|
|
||||||
self.ct_field.name: ContentType.objects.get_for_model(
|
|
||||||
self.instance, for_concrete_model=self.for_concrete_model),
|
|
||||||
self.ct_fk_field.name: self.instance.pk,
|
|
||||||
})
|
|
||||||
super(BaseGenericInlineFormSet, self).__init__(
|
|
||||||
queryset=qs, data=data, files=files,
|
|
||||||
prefix=prefix,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_default_prefix(cls):
|
|
||||||
opts = cls.model._meta
|
|
||||||
return '-'.join(
|
|
||||||
(opts.app_label, opts.model_name,
|
|
||||||
cls.ct_field.name, cls.ct_fk_field.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
def save_new(self, form, commit=True):
|
|
||||||
setattr(form.instance, self.ct_field.get_attname(),
|
|
||||||
ContentType.objects.get_for_model(self.instance).pk)
|
|
||||||
setattr(form.instance, self.ct_fk_field.get_attname(),
|
|
||||||
self.instance.pk)
|
|
||||||
return form.save(commit=commit)
|
|
||||||
|
|
||||||
|
|
||||||
def generic_inlineformset_factory(model, form=ModelForm,
|
|
||||||
formset=BaseGenericInlineFormSet,
|
|
||||||
ct_field="content_type", fk_field="object_id",
|
|
||||||
fields=None, exclude=None,
|
|
||||||
extra=3, can_order=False, can_delete=True,
|
|
||||||
max_num=None,
|
|
||||||
formfield_callback=None, validate_max=False,
|
|
||||||
for_concrete_model=True):
|
|
||||||
"""
|
|
||||||
Returns a ``GenericInlineFormSet`` for the given kwargs.
|
|
||||||
|
|
||||||
You must provide ``ct_field`` and ``fk_field`` if they are different from
|
|
||||||
the defaults ``content_type`` and ``object_id`` respectively.
|
|
||||||
"""
|
|
||||||
opts = model._meta
|
|
||||||
# if there is no field called `ct_field` let the exception propagate
|
|
||||||
ct_field = opts.get_field(ct_field)
|
|
||||||
if not isinstance(ct_field, models.ForeignKey) or ct_field.rel.to != ContentType:
|
|
||||||
raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field)
|
|
||||||
fk_field = opts.get_field(fk_field) # let the exception propagate
|
|
||||||
if exclude is not None:
|
|
||||||
exclude = list(exclude)
|
|
||||||
exclude.extend([ct_field.name, fk_field.name])
|
|
||||||
else:
|
|
||||||
exclude = [ct_field.name, fk_field.name]
|
|
||||||
FormSet = modelformset_factory(model, form=form,
|
|
||||||
formfield_callback=formfield_callback,
|
|
||||||
formset=formset,
|
|
||||||
extra=extra, can_delete=can_delete, can_order=can_order,
|
|
||||||
fields=fields, exclude=exclude, max_num=max_num,
|
|
||||||
validate_max=validate_max)
|
|
||||||
FormSet.ct_field = ct_field
|
|
||||||
FormSet.ct_fk_field = fk_field
|
|
||||||
FormSet.for_concrete_model = for_concrete_model
|
|
||||||
return FormSet
|
|
||||||
|
|
||||||
|
|
||||||
class GenericInlineModelAdmin(InlineModelAdmin):
|
|
||||||
ct_field = "content_type"
|
|
||||||
ct_fk_field = "object_id"
|
|
||||||
formset = BaseGenericInlineFormSet
|
|
||||||
|
|
||||||
def get_formset(self, request, obj=None, **kwargs):
|
|
||||||
if 'fields' in kwargs:
|
|
||||||
fields = kwargs.pop('fields')
|
|
||||||
else:
|
|
||||||
fields = flatten_fieldsets(self.get_fieldsets(request, obj))
|
|
||||||
if self.exclude is None:
|
|
||||||
exclude = []
|
|
||||||
else:
|
|
||||||
exclude = list(self.exclude)
|
|
||||||
exclude.extend(self.get_readonly_fields(request, obj))
|
|
||||||
if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
|
|
||||||
# Take the custom ModelForm's Meta.exclude into account only if the
|
|
||||||
# GenericInlineModelAdmin doesn't define its own.
|
|
||||||
exclude.extend(self.form._meta.exclude)
|
|
||||||
exclude = exclude or None
|
|
||||||
can_delete = self.can_delete and self.has_delete_permission(request, obj)
|
|
||||||
defaults = {
|
|
||||||
"ct_field": self.ct_field,
|
|
||||||
"fk_field": self.ct_fk_field,
|
|
||||||
"form": self.form,
|
|
||||||
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
|
|
||||||
"formset": self.formset,
|
|
||||||
"extra": self.extra,
|
|
||||||
"can_delete": can_delete,
|
|
||||||
"can_order": False,
|
|
||||||
"fields": fields,
|
|
||||||
"max_num": self.max_num,
|
|
||||||
"exclude": exclude
|
|
||||||
}
|
|
||||||
defaults.update(kwargs)
|
|
||||||
|
|
||||||
if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
|
|
||||||
defaults['fields'] = ALL_FIELDS
|
|
||||||
|
|
||||||
return generic_inlineformset_factory(self.model, **defaults)
|
|
||||||
|
|
||||||
|
|
||||||
class GenericStackedInline(GenericInlineModelAdmin):
|
|
||||||
template = 'admin/edit_inline/stacked.html'
|
|
||||||
|
|
||||||
|
|
||||||
class GenericTabularInline(GenericInlineModelAdmin):
|
|
||||||
template = 'admin/edit_inline/tabular.html'
|
|
||||||
|
|
|
@ -2187,30 +2187,32 @@ It is possible to use an inline with generically related objects. Let's say
|
||||||
you have the following models::
|
you have the following models::
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
|
||||||
class Image(models.Model):
|
class Image(models.Model):
|
||||||
image = models.ImageField(upload_to="images")
|
image = models.ImageField(upload_to="images")
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey("content_type", "object_id")
|
content_object = GenericForeignKey("content_type", "object_id")
|
||||||
|
|
||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
|
|
||||||
If you want to allow editing and creating ``Image`` instance on the ``Product``
|
If you want to allow editing and creating ``Image`` instance on the ``Product``
|
||||||
add/change views you can use ``GenericTabularInline`` or
|
add/change views you can use :class:`~django.contrib.contenttypes.admin.GenericTabularInline`
|
||||||
``GenericStackedInline`` (both subclasses of ``GenericInlineModelAdmin``)
|
or :class:`~django.contrib.contenttypes.admin.GenericStackedInline` (both
|
||||||
provided by ``django.contrib.contenttypes.generic``, they implement tabular and
|
subclasses of :class:`~django.contrib.contenttypes.admin.GenericInlineModelAdmin`)
|
||||||
|
provided by :mod:`~django.contrib.contenttypes.admin`, they implement tabular and
|
||||||
stacked visual layouts for the forms representing the inline objects
|
stacked visual layouts for the forms representing the inline objects
|
||||||
respectively just like their non-generic counterparts and behave just like any
|
respectively just like their non-generic counterparts and behave just like any
|
||||||
other inline. In your ``admin.py`` for this example app::
|
other inline. In your ``admin.py`` for this example app::
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.admin import GenericTabularInline
|
||||||
|
|
||||||
from myproject.myapp.models import Image, Product
|
from myproject.myapp.models import Image, Product
|
||||||
|
|
||||||
class ImageInline(generic.GenericTabularInline):
|
class ImageInline(GenericTabularInline):
|
||||||
model = Image
|
model = Image
|
||||||
|
|
||||||
class ProductAdmin(admin.ModelAdmin):
|
class ProductAdmin(admin.ModelAdmin):
|
||||||
|
|
|
@ -23,12 +23,12 @@ The built-in comment models
|
||||||
|
|
||||||
.. attribute:: content_object
|
.. attribute:: content_object
|
||||||
|
|
||||||
A :class:`~django.contrib.contenttypes.generic.GenericForeignKey`
|
A :class:`~django.contrib.contenttypes.fields.GenericForeignKey`
|
||||||
attribute pointing to the object the comment is attached to. You can use
|
attribute pointing to the object the comment is attached to. You can use
|
||||||
this to get at the related object (i.e. ``my_comment.content_object``).
|
this to get at the related object (i.e. ``my_comment.content_object``).
|
||||||
|
|
||||||
Since this field is a
|
Since this field is a
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey`, it's
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey`, it's
|
||||||
actually syntactic sugar on top of two underlying attributes, described
|
actually syntactic sugar on top of two underlying attributes, described
|
||||||
below.
|
below.
|
||||||
|
|
||||||
|
|
|
@ -232,7 +232,7 @@ lookup::
|
||||||
>>> user_type
|
>>> user_type
|
||||||
<ContentType: user>
|
<ContentType: user>
|
||||||
|
|
||||||
.. module:: django.contrib.contenttypes.generic
|
.. module:: django.contrib.contenttypes.fields
|
||||||
|
|
||||||
.. _generic-relations:
|
.. _generic-relations:
|
||||||
|
|
||||||
|
@ -250,14 +250,14 @@ generic (sometimes called "polymorphic") relationships between models.
|
||||||
A simple example is a tagging system, which might look like this::
|
A simple example is a tagging system, which might look like this::
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.contenttypes import generic
|
|
||||||
|
|
||||||
class TaggedItem(models.Model):
|
class TaggedItem(models.Model):
|
||||||
tag = models.SlugField()
|
tag = models.SlugField()
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
||||||
# On Python 3: def __str__(self):
|
# On Python 3: def __str__(self):
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
|
@ -274,7 +274,7 @@ model:
|
||||||
.. class:: GenericForeignKey
|
.. class:: GenericForeignKey
|
||||||
|
|
||||||
There are three parts to setting up a
|
There are three parts to setting up a
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey`:
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey`:
|
||||||
|
|
||||||
1. Give your model a :class:`~django.db.models.ForeignKey`
|
1. Give your model a :class:`~django.db.models.ForeignKey`
|
||||||
to :class:`~django.contrib.contenttypes.models.ContentType`. The usual
|
to :class:`~django.contrib.contenttypes.models.ContentType`. The usual
|
||||||
|
@ -286,11 +286,11 @@ model:
|
||||||
for this field is "object_id".
|
for this field is "object_id".
|
||||||
|
|
||||||
3. Give your model a
|
3. Give your model a
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey`, and
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey`, and
|
||||||
pass it the names of the two fields described above. If these fields
|
pass it the names of the two fields described above. If these fields
|
||||||
are named "content_type" and "object_id", you can omit this -- those
|
are named "content_type" and "object_id", you can omit this -- those
|
||||||
are the default field names
|
are the default field names
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` will
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey` will
|
||||||
look for.
|
look for.
|
||||||
|
|
||||||
.. attribute:: GenericForeignKey.for_concrete_model
|
.. attribute:: GenericForeignKey.for_concrete_model
|
||||||
|
@ -301,6 +301,10 @@ model:
|
||||||
is ``True``. This mirrors the ``for_concrete_model`` argument to
|
is ``True``. This mirrors the ``for_concrete_model`` argument to
|
||||||
:meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_model`.
|
:meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_model`.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
This class used to be defined in ``django.contrib.contenttypes.generic``.
|
||||||
|
|
||||||
|
|
||||||
.. admonition:: Primary key type compatibility
|
.. admonition:: Primary key type compatibility
|
||||||
|
|
||||||
|
@ -347,10 +351,10 @@ creating a ``TaggedItem``::
|
||||||
>>> t.content_object
|
>>> t.content_object
|
||||||
<User: Guido>
|
<User: Guido>
|
||||||
|
|
||||||
Due to the way :class:`~django.contrib.contenttypes.generic.GenericForeignKey`
|
Due to the way :class:`~django.contrib.contenttypes.fields.GenericForeignKey`
|
||||||
is implemented, you cannot use such fields directly with filters (``filter()``
|
is implemented, you cannot use such fields directly with filters (``filter()``
|
||||||
and ``exclude()``, for example) via the database API. Because a
|
and ``exclude()``, for example) via the database API. Because a
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` isn't a
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey` isn't a
|
||||||
normal field object, these examples will *not* work::
|
normal field object, these examples will *not* work::
|
||||||
|
|
||||||
# This will fail
|
# This will fail
|
||||||
|
@ -358,7 +362,7 @@ normal field object, these examples will *not* work::
|
||||||
# This will also fail
|
# This will also fail
|
||||||
>>> TaggedItem.objects.get(content_object=guido)
|
>>> TaggedItem.objects.get(content_object=guido)
|
||||||
|
|
||||||
Likewise, :class:`~django.contrib.contenttypes.generic.GenericForeignKey`\s
|
Likewise, :class:`~django.contrib.contenttypes.fields.GenericForeignKey`\s
|
||||||
does not appear in :class:`~django.forms.ModelForm`\s.
|
does not appear in :class:`~django.forms.ModelForm`\s.
|
||||||
|
|
||||||
Reverse generic relations
|
Reverse generic relations
|
||||||
|
@ -366,12 +370,16 @@ Reverse generic relations
|
||||||
|
|
||||||
.. class:: GenericRelation
|
.. class:: GenericRelation
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
This class used to be defined in ``django.contrib.contenttypes.generic``.
|
||||||
|
|
||||||
If you know which models you'll be using most often, you can also add
|
If you know which models you'll be using most often, you can also add
|
||||||
a "reverse" generic relationship to enable an additional API. For example::
|
a "reverse" generic relationship to enable an additional API. For example::
|
||||||
|
|
||||||
class Bookmark(models.Model):
|
class Bookmark(models.Model):
|
||||||
url = models.URLField()
|
url = models.URLField()
|
||||||
tags = generic.GenericRelation(TaggedItem)
|
tags = GenericRelation(TaggedItem)
|
||||||
|
|
||||||
``Bookmark`` instances will each have a ``tags`` attribute, which can
|
``Bookmark`` instances will each have a ``tags`` attribute, which can
|
||||||
be used to retrieve their associated ``TaggedItems``::
|
be used to retrieve their associated ``TaggedItems``::
|
||||||
|
@ -385,10 +393,10 @@ be used to retrieve their associated ``TaggedItems``::
|
||||||
>>> b.tags.all()
|
>>> b.tags.all()
|
||||||
[<TaggedItem: django>, <TaggedItem: python>]
|
[<TaggedItem: django>, <TaggedItem: python>]
|
||||||
|
|
||||||
Just as :class:`~django.contrib.contenttypes.generic.GenericForeignKey`
|
Just as :class:`~django.contrib.contenttypes.fields.GenericForeignKey`
|
||||||
accepts the names of the content-type and object-ID fields as
|
accepts the names of the content-type and object-ID fields as
|
||||||
arguments, so too does
|
arguments, so too does
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericRelation`;
|
:class:`~django.contrib.contenttypes.fields.GenericRelation`;
|
||||||
if the model which has the generic foreign key is using non-default names
|
if the model which has the generic foreign key is using non-default names
|
||||||
for those fields, you must pass the names of the fields when setting up a
|
for those fields, you must pass the names of the fields when setting up a
|
||||||
:class:`.GenericRelation` to it. For example, if the ``TaggedItem`` model
|
:class:`.GenericRelation` to it. For example, if the ``TaggedItem`` model
|
||||||
|
@ -396,9 +404,9 @@ referred to above used fields named ``content_type_fk`` and
|
||||||
``object_primary_key`` to create its generic foreign key, then a
|
``object_primary_key`` to create its generic foreign key, then a
|
||||||
:class:`.GenericRelation` back to it would need to be defined like so::
|
:class:`.GenericRelation` back to it would need to be defined like so::
|
||||||
|
|
||||||
tags = generic.GenericRelation(TaggedItem,
|
tags = GenericRelation(TaggedItem,
|
||||||
content_type_field='content_type_fk',
|
content_type_field='content_type_fk',
|
||||||
object_id_field='object_primary_key')
|
object_id_field='object_primary_key')
|
||||||
|
|
||||||
Of course, if you don't add the reverse relationship, you can do the
|
Of course, if you don't add the reverse relationship, you can do the
|
||||||
same types of lookups manually::
|
same types of lookups manually::
|
||||||
|
@ -410,29 +418,29 @@ same types of lookups manually::
|
||||||
[<TaggedItem: django>, <TaggedItem: python>]
|
[<TaggedItem: django>, <TaggedItem: python>]
|
||||||
|
|
||||||
Note that if the model in a
|
Note that if the model in a
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericRelation` uses a
|
:class:`~django.contrib.contenttypes.fields.GenericRelation` uses a
|
||||||
non-default value for ``ct_field`` or ``fk_field`` in its
|
non-default value for ``ct_field`` or ``fk_field`` in its
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` (e.g. the
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey` (e.g. the
|
||||||
:mod:`django.contrib.comments` app uses ``ct_field="object_pk"``),
|
:mod:`django.contrib.comments` app uses ``ct_field="object_pk"``),
|
||||||
you'll need to set ``content_type_field`` and/or ``object_id_field`` in
|
you'll need to set ``content_type_field`` and/or ``object_id_field`` in
|
||||||
the :class:`~django.contrib.contenttypes.generic.GenericRelation` to
|
the :class:`~django.contrib.contenttypes.fields.GenericRelation` to
|
||||||
match the ``ct_field`` and ``fk_field``, respectively, in the
|
match the ``ct_field`` and ``fk_field``, respectively, in the
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey`::
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey`::
|
||||||
|
|
||||||
comments = generic.GenericRelation(Comment, object_id_field="object_pk")
|
comments = fields.GenericRelation(Comment, object_id_field="object_pk")
|
||||||
|
|
||||||
Note also, that if you delete an object that has a
|
Note also, that if you delete an object that has a
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericRelation`, any objects
|
:class:`~django.contrib.contenttypes.fields.GenericRelation`, any objects
|
||||||
which have a :class:`~django.contrib.contenttypes.generic.GenericForeignKey`
|
which have a :class:`~django.contrib.contenttypes.fields.GenericForeignKey`
|
||||||
pointing at it will be deleted as well. In the example above, this means that
|
pointing at it will be deleted as well. In the example above, this means that
|
||||||
if a ``Bookmark`` object were deleted, any ``TaggedItem`` objects pointing at
|
if a ``Bookmark`` object were deleted, any ``TaggedItem`` objects pointing at
|
||||||
it would be deleted at the same time.
|
it would be deleted at the same time.
|
||||||
|
|
||||||
Unlike :class:`~django.db.models.ForeignKey`,
|
Unlike :class:`~django.db.models.ForeignKey`,
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` does not accept
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey` does not accept
|
||||||
an :attr:`~django.db.models.ForeignKey.on_delete` argument to customize this
|
an :attr:`~django.db.models.ForeignKey.on_delete` argument to customize this
|
||||||
behavior; if desired, you can avoid the cascade-deletion simply by not using
|
behavior; if desired, you can avoid the cascade-deletion simply by not using
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericRelation`, and alternate
|
:class:`~django.contrib.contenttypes.fields.GenericRelation`, and alternate
|
||||||
behavior can be provided via the :data:`~django.db.models.signals.pre_delete`
|
behavior can be provided via the :data:`~django.db.models.signals.pre_delete`
|
||||||
signal.
|
signal.
|
||||||
|
|
||||||
|
@ -441,7 +449,7 @@ Generic relations and aggregation
|
||||||
|
|
||||||
:doc:`Django's database aggregation API </topics/db/aggregation>`
|
:doc:`Django's database aggregation API </topics/db/aggregation>`
|
||||||
doesn't work with a
|
doesn't work with a
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericRelation`. For example, you
|
:class:`~django.contrib.contenttypes.fields.GenericRelation`. For example, you
|
||||||
might be tempted to try something like::
|
might be tempted to try something like::
|
||||||
|
|
||||||
Bookmark.objects.aggregate(Count('tags'))
|
Bookmark.objects.aggregate(Count('tags'))
|
||||||
|
@ -452,47 +460,23 @@ to the queryset to ensure the correct content type, but the
|
||||||
into account. For now, if you need aggregates on generic relations, you'll
|
into account. For now, if you need aggregates on generic relations, you'll
|
||||||
need to calculate them without using the aggregation API.
|
need to calculate them without using the aggregation API.
|
||||||
|
|
||||||
Generic relations in forms and admin
|
|
||||||
------------------------------------
|
|
||||||
|
|
||||||
The :mod:`django.contrib.contenttypes.generic` module provides:
|
.. module:: django.contrib.contenttypes.forms
|
||||||
|
|
||||||
* ``BaseGenericInlineFormSet``
|
Generic relation in forms
|
||||||
* :class:`~django.contrib.contenttypes.generic.GenericTabularInline`
|
-------------------------
|
||||||
and :class:`~django.contrib.contenttypes.generic.GenericStackedInline`
|
|
||||||
(subclasses of
|
The :mod:`django.contrib.contenttypes.forms` module provides:
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericInlineModelAdmin`)
|
|
||||||
|
* :class:`BaseGenericInlineFormSet`
|
||||||
* A formset factory, :func:`generic_inlineformset_factory`, for use with
|
* A formset factory, :func:`generic_inlineformset_factory`, for use with
|
||||||
:class:`GenericForeignKey`
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey`.
|
||||||
|
|
||||||
These classes and functions enable the use of generic relations in forms
|
.. class:: BaseGenericInlineFormSet
|
||||||
and the admin. See the :doc:`model formset </topics/forms/modelforms>` and
|
|
||||||
:ref:`admin <using-generic-relations-as-an-inline>` documentation for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
.. class:: GenericInlineModelAdmin
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
The :class:`~django.contrib.contenttypes.generic.GenericInlineModelAdmin`
|
This class used to be defined in ``django.contrib.contenttypes.generic``.
|
||||||
class inherits all properties from an
|
|
||||||
:class:`~django.contrib.admin.InlineModelAdmin` class. However,
|
|
||||||
it adds a couple of its own for working with the generic relation:
|
|
||||||
|
|
||||||
.. attribute:: ct_field
|
|
||||||
|
|
||||||
The name of the
|
|
||||||
:class:`~django.contrib.contenttypes.models.ContentType` foreign key
|
|
||||||
field on the model. Defaults to ``content_type``.
|
|
||||||
|
|
||||||
.. attribute:: ct_fk_field
|
|
||||||
|
|
||||||
The name of the integer field that represents the ID of the related
|
|
||||||
object. Defaults to ``object_id``.
|
|
||||||
|
|
||||||
.. class:: GenericTabularInline
|
|
||||||
.. class:: GenericStackedInline
|
|
||||||
|
|
||||||
Subclasses of :class:`GenericInlineModelAdmin` with stacked and tabular
|
|
||||||
layouts, respectively.
|
|
||||||
|
|
||||||
.. function:: generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field="content_type", fk_field="object_id", fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True)
|
.. function:: generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field="content_type", fk_field="object_id", fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True)
|
||||||
|
|
||||||
|
@ -508,5 +492,57 @@ information.
|
||||||
.. versionadded:: 1.6
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
The ``for_concrete_model`` argument corresponds to the
|
The ``for_concrete_model`` argument corresponds to the
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey.for_concrete_model`
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey.for_concrete_model`
|
||||||
argument on ``GenericForeignKey``.
|
argument on ``GenericForeignKey``.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
This function used to be defined in ``django.contrib.contenttypes.generic``.
|
||||||
|
|
||||||
|
|
||||||
|
.. module:: django.contrib.contenttypes.admin
|
||||||
|
|
||||||
|
Generic relations in admin
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
The :mod:`django.contrib.contenttypes.admin` module provides
|
||||||
|
:class:`~django.contrib.contenttypes.admin.GenericTabularInline` and
|
||||||
|
:class:`~django.contrib.contenttypes.admin.GenericStackedInline` (subclasses of
|
||||||
|
:class:`~django.contrib.contenttypes.admin.GenericInlineModelAdmin`)
|
||||||
|
|
||||||
|
These classes and functions enable the use of generic relations in forms
|
||||||
|
and the admin. See the :doc:`model formset </topics/forms/modelforms>` and
|
||||||
|
:ref:`admin <using-generic-relations-as-an-inline>` documentation for more
|
||||||
|
information.
|
||||||
|
|
||||||
|
.. class:: GenericInlineModelAdmin
|
||||||
|
|
||||||
|
The :class:`~django.contrib.contenttypes.admin.GenericInlineModelAdmin`
|
||||||
|
class inherits all properties from an
|
||||||
|
:class:`~django.contrib.admin.InlineModelAdmin` class. However,
|
||||||
|
it adds a couple of its own for working with the generic relation:
|
||||||
|
|
||||||
|
.. attribute:: ct_field
|
||||||
|
|
||||||
|
The name of the
|
||||||
|
:class:`~django.contrib.contenttypes.models.ContentType` foreign key
|
||||||
|
field on the model. Defaults to ``content_type``.
|
||||||
|
|
||||||
|
.. attribute:: ct_fk_field
|
||||||
|
|
||||||
|
The name of the integer field that represents the ID of the related
|
||||||
|
object. Defaults to ``object_id``.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
This class used to be defined in ``django.contrib.contenttypes.generic``.
|
||||||
|
|
||||||
|
.. class:: GenericTabularInline
|
||||||
|
.. class:: GenericStackedInline
|
||||||
|
|
||||||
|
Subclasses of :class:`GenericInlineModelAdmin` with stacked and tabular
|
||||||
|
layouts, respectively.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
|
These classes used to be defined in ``django.contrib.contenttypes.generic``.
|
||||||
|
|
|
@ -798,8 +798,8 @@ relationship, and does the 'joining' in Python. This allows it to prefetch
|
||||||
many-to-many and many-to-one objects, which cannot be done using
|
many-to-many and many-to-one objects, which cannot be done using
|
||||||
``select_related``, in addition to the foreign key and one-to-one relationships
|
``select_related``, in addition to the foreign key and one-to-one relationships
|
||||||
that are supported by ``select_related``. It also supports prefetching of
|
that are supported by ``select_related``. It also supports prefetching of
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericRelation` and
|
:class:`~django.contrib.contenttypes.fields.GenericRelation` and
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey`.
|
:class:`~django.contrib.contenttypes.fields.GenericForeignKey`.
|
||||||
|
|
||||||
For example, suppose you have these models::
|
For example, suppose you have these models::
|
||||||
|
|
||||||
|
|
|
@ -92,8 +92,7 @@ different strategy and broader scope,
|
||||||
``QuerySet`` that will prefetch each of the specified related lookups in a
|
``QuerySet`` that will prefetch each of the specified related lookups in a
|
||||||
single batch as soon as the query begins to be evaluated. Unlike
|
single batch as soon as the query begins to be evaluated. Unlike
|
||||||
``select_related``, it does the joins in Python, not in the database, and
|
``select_related``, it does the joins in Python, not in the database, and
|
||||||
supports many-to-many relationships,
|
supports many-to-many relationships, ``GenericForeignKey`` and more. This
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` and more. This
|
|
||||||
allows you to fix a very common performance problem in which your code ends up
|
allows you to fix a very common performance problem in which your code ends up
|
||||||
doing O(n) database queries (or worse) if objects on your primary ``QuerySet``
|
doing O(n) database queries (or worse) if objects on your primary ``QuerySet``
|
||||||
each have many related objects that you also need.
|
each have many related objects that you also need.
|
||||||
|
|
|
@ -108,8 +108,7 @@ different strategy and broader scope,
|
||||||
``QuerySet`` that will prefetch each of the specified related lookups in a
|
``QuerySet`` that will prefetch each of the specified related lookups in a
|
||||||
single batch as soon as the query begins to be evaluated. Unlike
|
single batch as soon as the query begins to be evaluated. Unlike
|
||||||
``select_related``, it does the joins in Python, not in the database, and
|
``select_related``, it does the joins in Python, not in the database, and
|
||||||
supports many-to-many relationships,
|
supports many-to-many relationships, ``GenericForeignKey`` and more. This
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` and more. This
|
|
||||||
allows you to fix a very common performance problem in which your code ends up
|
allows you to fix a very common performance problem in which your code ends up
|
||||||
doing O(n) database queries (or worse) if objects on your primary ``QuerySet``
|
doing O(n) database queries (or worse) if objects on your primary ``QuerySet``
|
||||||
each have many related objects that you also need.
|
each have many related objects that you also need.
|
||||||
|
|
|
@ -257,8 +257,7 @@ different strategy and broader scope,
|
||||||
``QuerySet`` that will prefetch each of the specified related lookups in a
|
``QuerySet`` that will prefetch each of the specified related lookups in a
|
||||||
single batch as soon as the query begins to be evaluated. Unlike
|
single batch as soon as the query begins to be evaluated. Unlike
|
||||||
``select_related``, it does the joins in Python, not in the database, and
|
``select_related``, it does the joins in Python, not in the database, and
|
||||||
supports many-to-many relationships,
|
supports many-to-many relationships, ``GenericForeignKey`` and more. This
|
||||||
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` and more. This
|
|
||||||
allows you to fix a very common performance problem in which your code ends up
|
allows you to fix a very common performance problem in which your code ends up
|
||||||
doing O(n) database queries (or worse) if objects on your primary ``QuerySet``
|
doing O(n) database queries (or worse) if objects on your primary ``QuerySet``
|
||||||
each have many related objects that you also need to fetch.
|
each have many related objects that you also need to fetch.
|
||||||
|
|
|
@ -302,11 +302,9 @@ Minor features
|
||||||
:class:`~django.views.generic.base.RedirectView` now support HTTP ``PATCH``
|
:class:`~django.views.generic.base.RedirectView` now support HTTP ``PATCH``
|
||||||
method.
|
method.
|
||||||
|
|
||||||
* :class:`GenericForeignKey <django.contrib.contenttypes.generic.GenericForeignKey>`
|
* ``GenericForeignKey`` now takes an optional ``for_concrete_model`` argument,
|
||||||
now takes an optional
|
which when set to ``False`` allows the field to reference proxy models. The
|
||||||
:attr:`~django.contrib.contenttypes.generic.GenericForeignKey.for_concrete_model`
|
default is ``True`` to retain the old behavior.
|
||||||
argument, which when set to ``False`` allows the field to reference proxy
|
|
||||||
models. The default is ``True`` to retain the old behavior.
|
|
||||||
|
|
||||||
* The :class:`~django.middleware.locale.LocaleMiddleware` now stores the active
|
* The :class:`~django.middleware.locale.LocaleMiddleware` now stores the active
|
||||||
language in session if it is not present there. This prevents loss of
|
language in session if it is not present there. This prevents loss of
|
||||||
|
|
|
@ -1030,6 +1030,25 @@ API, it will go through a regular deprecation path. This attribute was mostly
|
||||||
used by methods that bypassed ``ModelAdmin.get_fieldsets()`` but this was
|
used by methods that bypassed ``ModelAdmin.get_fieldsets()`` but this was
|
||||||
considered a bug and has been addressed.
|
considered a bug and has been addressed.
|
||||||
|
|
||||||
|
Reorganization of ``django.contrib.contenttypes``
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Since ``django.contrib.contenttypes.generic`` defined both admin and model
|
||||||
|
related objects an import of this module could trigger unexpected side effects.
|
||||||
|
As a consequence, its contents were split into :mod:`~django.contrib.contenttypes`
|
||||||
|
submodules and the ``django.contrib.contenttypes.generic`` module is deprecated:
|
||||||
|
|
||||||
|
* :class:`~django.contrib.contenttypes.fields.GenericForeignKey` and
|
||||||
|
:class:`~django.contrib.contenttypes.fields.GenericRelation` now live in
|
||||||
|
:mod:`~django.contrib.contenttypes.fields`.
|
||||||
|
* :class:`~django.contrib.contenttypes.forms.BaseGenericInlineFormSet` and
|
||||||
|
:func:`~django.contrib.contenttypes.forms.generic_inlineformset_factory` now
|
||||||
|
live in :mod:`~django.contrib.contenttypes.forms`.
|
||||||
|
* :class:`~django.contrib.contenttypes.admin.GenericInlineModelAdmin`,
|
||||||
|
:class:`~django.contrib.contenttypes.admin.GenericStackedInline` and
|
||||||
|
:class:`~django.contrib.contenttypes.admin.GenericTabularInline` now live in
|
||||||
|
:mod:`~django.contrib.contenttypes.admin`.
|
||||||
|
|
||||||
``syncdb``
|
``syncdb``
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ from __future__ import unicode_literals
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.contenttypes import generic
|
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ class Child(models.Model):
|
||||||
|
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
parent = generic.GenericForeignKey()
|
parent = GenericForeignKey()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'I am %s, a child of %s' % (self.name, self.parent)
|
return 'I am %s, a child of %s' % (self.name, self.parent)
|
||||||
|
|
|
@ -6,7 +6,9 @@ import tempfile
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.files.storage import FileSystemStorage
|
from django.core.files.storage import FileSystemStorage
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -430,7 +432,7 @@ class FunkyTag(models.Model):
|
||||||
name = models.CharField(max_length=25)
|
name = models.CharField(max_length=25)
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -441,7 +443,7 @@ class Plot(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
team_leader = models.ForeignKey(Villain, related_name='lead_plots')
|
team_leader = models.ForeignKey(Villain, related_name='lead_plots')
|
||||||
contact = models.ForeignKey(Villain, related_name='contact_plots')
|
contact = models.ForeignKey(Villain, related_name='contact_plots')
|
||||||
tags = generic.GenericRelation(FunkyTag)
|
tags = GenericRelation(FunkyTag)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
@ -28,7 +30,7 @@ class ItemTag(models.Model):
|
||||||
tag = models.CharField(max_length=100)
|
tag = models.CharField(max_length=100)
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
|
@ -42,7 +44,7 @@ class Book(models.Model):
|
||||||
contact = models.ForeignKey(Author, related_name='book_contact_set')
|
contact = models.ForeignKey(Author, related_name='book_contact_set')
|
||||||
publisher = models.ForeignKey(Publisher)
|
publisher = models.ForeignKey(Publisher)
|
||||||
pubdate = models.DateField()
|
pubdate = models.DateField()
|
||||||
tags = generic.GenericRelation(ItemTag)
|
tags = GenericRelation(ItemTag)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models, connection
|
from django.db import models, connection
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
@ -46,13 +48,13 @@ class Tag(models.Model):
|
||||||
name = models.CharField(max_length=30)
|
name = models.CharField(max_length=30)
|
||||||
content_type = models.ForeignKey(ContentType, related_name='backend_tags')
|
content_type = models.ForeignKey(ContentType, related_name='backend_tags')
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
||||||
|
|
||||||
class Post(models.Model):
|
class Post(models.Model):
|
||||||
name = models.CharField(max_length=30)
|
name = models.CharField(max_length=30)
|
||||||
text = models.TextField()
|
text = models.TextField()
|
||||||
tags = generic.GenericRelation('Tag')
|
tags = GenericRelation('Tag')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'CaseSensitive_Post'
|
db_table = 'CaseSensitive_Post'
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from django.apps.registry import Apps, apps
|
from django.apps.registry import Apps, apps
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core import checks
|
from django.core import checks
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -90,7 +92,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
|
||||||
|
|
||||||
def test_str(self):
|
def test_str(self):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
field = generic.GenericForeignKey()
|
field = GenericForeignKey()
|
||||||
expected = "contenttypes_tests.Model.field"
|
expected = "contenttypes_tests.Model.field"
|
||||||
actual = force_str(Model.field)
|
actual = force_str(Model.field)
|
||||||
self.assertEqual(expected, actual)
|
self.assertEqual(expected, actual)
|
||||||
|
@ -99,7 +101,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
|
||||||
class TaggedItem(models.Model):
|
class TaggedItem(models.Model):
|
||||||
# no content_type field
|
# no content_type field
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
errors = TaggedItem.content_object.check()
|
errors = TaggedItem.content_object.check()
|
||||||
expected = [
|
expected = [
|
||||||
|
@ -116,7 +118,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
content_type = models.IntegerField() # should be ForeignKey
|
content_type = models.IntegerField() # should be ForeignKey
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey(
|
content_object = GenericForeignKey(
|
||||||
'content_type', 'object_id')
|
'content_type', 'object_id')
|
||||||
|
|
||||||
errors = Model.content_object.check()
|
errors = Model.content_object.check()
|
||||||
|
@ -136,7 +138,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
content_type = models.ForeignKey('self') # should point to ContentType
|
content_type = models.ForeignKey('self') # should point to ContentType
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey(
|
content_object = GenericForeignKey(
|
||||||
'content_type', 'object_id')
|
'content_type', 'object_id')
|
||||||
|
|
||||||
errors = Model.content_object.check()
|
errors = Model.content_object.check()
|
||||||
|
@ -156,7 +158,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
|
||||||
class TaggedItem(models.Model):
|
class TaggedItem(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
# missing object_id field
|
# missing object_id field
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
errors = TaggedItem.content_object.check()
|
errors = TaggedItem.content_object.check()
|
||||||
expected = [
|
expected = [
|
||||||
|
@ -173,7 +175,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object_ = generic.GenericForeignKey(
|
content_object_ = GenericForeignKey(
|
||||||
'content_type', 'object_id')
|
'content_type', 'object_id')
|
||||||
|
|
||||||
errors = Model.content_object_.check()
|
errors = Model.content_object_.check()
|
||||||
|
@ -188,7 +190,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
|
||||||
self.assertEqual(errors, expected)
|
self.assertEqual(errors, expected)
|
||||||
|
|
||||||
def test_generic_foreign_key_checks_are_performed(self):
|
def test_generic_foreign_key_checks_are_performed(self):
|
||||||
class MyGenericForeignKey(generic.GenericForeignKey):
|
class MyGenericForeignKey(GenericForeignKey):
|
||||||
def check(self, **kwargs):
|
def check(self, **kwargs):
|
||||||
return ['performed!']
|
return ['performed!']
|
||||||
|
|
||||||
|
@ -205,10 +207,10 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
|
||||||
class TaggedItem(models.Model):
|
class TaggedItem(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
class Bookmark(models.Model):
|
class Bookmark(models.Model):
|
||||||
tags = generic.GenericRelation('TaggedItem')
|
tags = GenericRelation('TaggedItem')
|
||||||
|
|
||||||
errors = Bookmark.tags.field.check()
|
errors = Bookmark.tags.field.check()
|
||||||
self.assertEqual(errors, [])
|
self.assertEqual(errors, [])
|
||||||
|
@ -217,11 +219,11 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
|
||||||
class TaggedItem(models.Model):
|
class TaggedItem(models.Model):
|
||||||
custom_content_type = models.ForeignKey(ContentType)
|
custom_content_type = models.ForeignKey(ContentType)
|
||||||
custom_object_id = models.PositiveIntegerField()
|
custom_object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey(
|
content_object = GenericForeignKey(
|
||||||
'custom_content_type', 'custom_object_id')
|
'custom_content_type', 'custom_object_id')
|
||||||
|
|
||||||
class Bookmark(models.Model):
|
class Bookmark(models.Model):
|
||||||
tags = generic.GenericRelation('TaggedItem',
|
tags = GenericRelation('TaggedItem',
|
||||||
content_type_field='custom_content_type',
|
content_type_field='custom_content_type',
|
||||||
object_id_field='custom_object_id')
|
object_id_field='custom_object_id')
|
||||||
|
|
||||||
|
@ -230,7 +232,7 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
|
||||||
|
|
||||||
def test_pointing_to_missing_model(self):
|
def test_pointing_to_missing_model(self):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
rel = generic.GenericRelation('MissingModel')
|
rel = GenericRelation('MissingModel')
|
||||||
|
|
||||||
errors = Model.rel.field.check()
|
errors = Model.rel.field.check()
|
||||||
expected = [
|
expected = [
|
||||||
|
@ -248,10 +250,10 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
|
||||||
|
|
||||||
def test_valid_self_referential_generic_relationship(self):
|
def test_valid_self_referential_generic_relationship(self):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
rel = generic.GenericRelation('Model')
|
rel = GenericRelation('Model')
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey(
|
content_object = GenericForeignKey(
|
||||||
'content_type', 'object_id')
|
'content_type', 'object_id')
|
||||||
|
|
||||||
errors = Model.rel.field.check()
|
errors = Model.rel.field.check()
|
||||||
|
@ -261,10 +263,10 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
|
||||||
class TaggedItem(models.Model):
|
class TaggedItem(models.Model):
|
||||||
# no content_type field
|
# no content_type field
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
class Bookmark(models.Model):
|
class Bookmark(models.Model):
|
||||||
tags = generic.GenericRelation('TaggedItem')
|
tags = GenericRelation('TaggedItem')
|
||||||
|
|
||||||
errors = Bookmark.tags.field.check()
|
errors = Bookmark.tags.field.check()
|
||||||
expected = [
|
expected = [
|
||||||
|
@ -281,10 +283,10 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
|
||||||
class TaggedItem(models.Model):
|
class TaggedItem(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
# missing object_id field
|
# missing object_id field
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
class Bookmark(models.Model):
|
class Bookmark(models.Model):
|
||||||
tags = generic.GenericRelation('TaggedItem')
|
tags = GenericRelation('TaggedItem')
|
||||||
|
|
||||||
errors = Bookmark.tags.field.check()
|
errors = Bookmark.tags.field.check()
|
||||||
expected = [
|
expected = [
|
||||||
|
@ -303,7 +305,7 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
|
|
||||||
class Bookmark(models.Model):
|
class Bookmark(models.Model):
|
||||||
tags = generic.GenericRelation('TaggedItem')
|
tags = GenericRelation('TaggedItem')
|
||||||
|
|
||||||
errors = Bookmark.tags.field.check()
|
errors = Bookmark.tags.field.check()
|
||||||
expected = [
|
expected = [
|
||||||
|
@ -326,13 +328,13 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
|
||||||
class SwappedModel(models.Model):
|
class SwappedModel(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
swappable = 'TEST_SWAPPED_MODEL'
|
swappable = 'TEST_SWAPPED_MODEL'
|
||||||
|
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
rel = generic.GenericRelation('SwappedModel')
|
rel = GenericRelation('SwappedModel')
|
||||||
|
|
||||||
errors = Model.rel.field.check()
|
errors = Model.rel.field.check()
|
||||||
expected = [
|
expected = [
|
||||||
|
@ -351,10 +353,10 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
|
||||||
class TaggedItem(models.Model):
|
class TaggedItem(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
class InvalidBookmark(models.Model):
|
class InvalidBookmark(models.Model):
|
||||||
tags_ = generic.GenericRelation('TaggedItem')
|
tags_ = GenericRelation('TaggedItem')
|
||||||
|
|
||||||
errors = InvalidBookmark.tags_.field.check()
|
errors = InvalidBookmark.tags_.field.check()
|
||||||
expected = [
|
expected = [
|
||||||
|
|
|
@ -11,7 +11,9 @@ returns.
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
|
||||||
|
@ -88,7 +90,7 @@ class Person(models.Model):
|
||||||
favorite_book = models.ForeignKey('Book', null=True, related_name='favorite_books')
|
favorite_book = models.ForeignKey('Book', null=True, related_name='favorite_books')
|
||||||
favorite_thing_type = models.ForeignKey('contenttypes.ContentType', null=True)
|
favorite_thing_type = models.ForeignKey('contenttypes.ContentType', null=True)
|
||||||
favorite_thing_id = models.IntegerField(null=True)
|
favorite_thing_id = models.IntegerField(null=True)
|
||||||
favorite_thing = generic.GenericForeignKey('favorite_thing_type', 'favorite_thing_id')
|
favorite_thing = GenericForeignKey('favorite_thing_type', 'favorite_thing_id')
|
||||||
|
|
||||||
objects = PersonManager()
|
objects = PersonManager()
|
||||||
fun_people = FunPeopleManager()
|
fun_people = FunPeopleManager()
|
||||||
|
@ -110,7 +112,7 @@ class FunPerson(models.Model):
|
||||||
favorite_book = models.ForeignKey('Book', null=True, related_name='fun_people_favorite_books')
|
favorite_book = models.ForeignKey('Book', null=True, related_name='fun_people_favorite_books')
|
||||||
favorite_thing_type = models.ForeignKey('contenttypes.ContentType', null=True)
|
favorite_thing_type = models.ForeignKey('contenttypes.ContentType', null=True)
|
||||||
favorite_thing_id = models.IntegerField(null=True)
|
favorite_thing_id = models.IntegerField(null=True)
|
||||||
favorite_thing = generic.GenericForeignKey('favorite_thing_type', 'favorite_thing_id')
|
favorite_thing = GenericForeignKey('favorite_thing_type', 'favorite_thing_id')
|
||||||
|
|
||||||
objects = FunPeopleManager()
|
objects = FunPeopleManager()
|
||||||
|
|
||||||
|
@ -127,10 +129,10 @@ class Book(models.Model):
|
||||||
authors = models.ManyToManyField(Person, related_name='books')
|
authors = models.ManyToManyField(Person, related_name='books')
|
||||||
fun_authors = models.ManyToManyField(FunPerson, related_name='books')
|
fun_authors = models.ManyToManyField(FunPerson, related_name='books')
|
||||||
|
|
||||||
favorite_things = generic.GenericRelation(Person,
|
favorite_things = GenericRelation(Person,
|
||||||
content_type_field='favorite_thing_type', object_id_field='favorite_thing_id')
|
content_type_field='favorite_thing_type', object_id_field='favorite_thing_id')
|
||||||
|
|
||||||
fun_people_favorite_things = generic.GenericRelation(FunPerson,
|
fun_people_favorite_things = GenericRelation(FunPerson,
|
||||||
content_type_field='favorite_thing_type', object_id_field='favorite_thing_id')
|
content_type_field='favorite_thing_type', object_id_field='favorite_thing_id')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
@ -7,7 +9,7 @@ class Award(models.Model):
|
||||||
name = models.CharField(max_length=25)
|
name = models.CharField(max_length=25)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
|
|
||||||
class AwardNote(models.Model):
|
class AwardNote(models.Model):
|
||||||
|
@ -17,7 +19,7 @@ class AwardNote(models.Model):
|
||||||
|
|
||||||
class Person(models.Model):
|
class Person(models.Model):
|
||||||
name = models.CharField(max_length=25)
|
name = models.CharField(max_length=25)
|
||||||
awards = generic.GenericRelation(Award)
|
awards = GenericRelation(Award)
|
||||||
|
|
||||||
|
|
||||||
class Book(models.Model):
|
class Book(models.Model):
|
||||||
|
|
|
@ -9,7 +9,7 @@ in the application directory, or in one of the directories named in the
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
@ -55,8 +55,7 @@ class Tag(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
tagged_type = models.ForeignKey(ContentType, related_name="fixtures_tag_set")
|
tagged_type = models.ForeignKey(ContentType, related_name="fixtures_tag_set")
|
||||||
tagged_id = models.PositiveIntegerField(default=0)
|
tagged_id = models.PositiveIntegerField(default=0)
|
||||||
tagged = generic.GenericForeignKey(ct_field='tagged_type',
|
tagged = GenericForeignKey(ct_field='tagged_type', fk_field='tagged_id')
|
||||||
fk_field='tagged_id')
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '<%s: %s> tagged "%s"' % (self.tagged.__class__.__name__,
|
return '<%s: %s> tagged "%s"' % (self.tagged.__class__.__name__,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.admin import GenericTabularInline
|
||||||
|
|
||||||
from .models import (Media, PhoneNumber, Episode, EpisodeExtra, Contact,
|
from .models import (Media, PhoneNumber, Episode, EpisodeExtra, Contact,
|
||||||
Category, EpisodePermanent, EpisodeMaxNum)
|
Category, EpisodePermanent, EpisodeMaxNum)
|
||||||
|
@ -8,7 +8,7 @@ from .models import (Media, PhoneNumber, Episode, EpisodeExtra, Contact,
|
||||||
site = admin.AdminSite(name="admin")
|
site = admin.AdminSite(name="admin")
|
||||||
|
|
||||||
|
|
||||||
class MediaInline(generic.GenericTabularInline):
|
class MediaInline(GenericTabularInline):
|
||||||
model = Media
|
model = Media
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,22 +18,22 @@ class EpisodeAdmin(admin.ModelAdmin):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class MediaExtraInline(generic.GenericTabularInline):
|
class MediaExtraInline(GenericTabularInline):
|
||||||
model = Media
|
model = Media
|
||||||
extra = 0
|
extra = 0
|
||||||
|
|
||||||
|
|
||||||
class MediaMaxNumInline(generic.GenericTabularInline):
|
class MediaMaxNumInline(GenericTabularInline):
|
||||||
model = Media
|
model = Media
|
||||||
extra = 5
|
extra = 5
|
||||||
max_num = 2
|
max_num = 2
|
||||||
|
|
||||||
|
|
||||||
class PhoneNumberInline(generic.GenericTabularInline):
|
class PhoneNumberInline(GenericTabularInline):
|
||||||
model = PhoneNumber
|
model = PhoneNumber
|
||||||
|
|
||||||
|
|
||||||
class MediaPermanentInline(generic.GenericTabularInline):
|
class MediaPermanentInline(GenericTabularInline):
|
||||||
model = Media
|
model = Media
|
||||||
can_delete = False
|
can_delete = False
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
@ -17,7 +19,7 @@ class Media(models.Model):
|
||||||
"""
|
"""
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
url = models.URLField()
|
url = models.URLField()
|
||||||
description = models.CharField(max_length=100, blank=True)
|
description = models.CharField(max_length=100, blank=True)
|
||||||
keywords = models.CharField(max_length=100, blank=True)
|
keywords = models.CharField(max_length=100, blank=True)
|
||||||
|
@ -56,7 +58,7 @@ class Category(models.Model):
|
||||||
class PhoneNumber(models.Model):
|
class PhoneNumber(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = GenericForeignKey('content_type', 'object_id')
|
||||||
phone_number = models.CharField(max_length=30)
|
phone_number = models.CharField(max_length=30)
|
||||||
category = models.ForeignKey(Category, null=True, blank=True)
|
category = models.ForeignKey(Category, null=True, blank=True)
|
||||||
|
|
||||||
|
@ -66,7 +68,7 @@ class PhoneNumber(models.Model):
|
||||||
|
|
||||||
class Contact(models.Model):
|
class Contact(models.Model):
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
phone_numbers = generic.GenericRelation(PhoneNumber)
|
phone_numbers = GenericRelation(PhoneNumber)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -4,8 +4,8 @@ import warnings
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.admin.sites import AdminSite
|
from django.contrib.admin.sites import AdminSite
|
||||||
from django.contrib.contenttypes.generic import (
|
from django.contrib.contenttypes.admin import GenericTabularInline
|
||||||
generic_inlineformset_factory, GenericTabularInline)
|
from django.contrib.contenttypes.forms import generic_inlineformset_factory
|
||||||
from django.forms.formsets import DEFAULT_MAX_NUM
|
from django.forms.formsets import DEFAULT_MAX_NUM
|
||||||
from django.forms.models import ModelForm
|
from django.forms.models import ModelForm
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
|
|
@ -11,7 +11,9 @@ from complete).
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
@ -24,7 +26,7 @@ class TaggedItem(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
|
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["tag", "content_type__name"]
|
ordering = ["tag", "content_type__name"]
|
||||||
|
@ -43,7 +45,7 @@ class AbstractComparison(models.Model):
|
||||||
content_type1 = models.ForeignKey(ContentType, related_name="comparative1_set")
|
content_type1 = models.ForeignKey(ContentType, related_name="comparative1_set")
|
||||||
object_id1 = models.PositiveIntegerField()
|
object_id1 = models.PositiveIntegerField()
|
||||||
|
|
||||||
first_obj = generic.GenericForeignKey(ct_field="content_type1", fk_field="object_id1")
|
first_obj = GenericForeignKey(ct_field="content_type1", fk_field="object_id1")
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
|
@ -55,7 +57,7 @@ class Comparison(AbstractComparison):
|
||||||
content_type2 = models.ForeignKey(ContentType, related_name="comparative2_set")
|
content_type2 = models.ForeignKey(ContentType, related_name="comparative2_set")
|
||||||
object_id2 = models.PositiveIntegerField()
|
object_id2 = models.PositiveIntegerField()
|
||||||
|
|
||||||
other_obj = generic.GenericForeignKey(ct_field="content_type2", fk_field="object_id2")
|
other_obj = GenericForeignKey(ct_field="content_type2", fk_field="object_id2")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s is %s than %s" % (self.first_obj, self.comparative, self.other_obj)
|
return "%s is %s than %s" % (self.first_obj, self.comparative, self.other_obj)
|
||||||
|
@ -66,10 +68,10 @@ class Animal(models.Model):
|
||||||
common_name = models.CharField(max_length=150)
|
common_name = models.CharField(max_length=150)
|
||||||
latin_name = models.CharField(max_length=150)
|
latin_name = models.CharField(max_length=150)
|
||||||
|
|
||||||
tags = generic.GenericRelation(TaggedItem)
|
tags = GenericRelation(TaggedItem)
|
||||||
comparisons = generic.GenericRelation(Comparison,
|
comparisons = GenericRelation(Comparison,
|
||||||
object_id_field="object_id1",
|
object_id_field="object_id1",
|
||||||
content_type_field="content_type1")
|
content_type_field="content_type1")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.common_name
|
return self.common_name
|
||||||
|
@ -80,7 +82,7 @@ class Vegetable(models.Model):
|
||||||
name = models.CharField(max_length=150)
|
name = models.CharField(max_length=150)
|
||||||
is_yucky = models.BooleanField(default=True)
|
is_yucky = models.BooleanField(default=True)
|
||||||
|
|
||||||
tags = generic.GenericRelation(TaggedItem)
|
tags = GenericRelation(TaggedItem)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -109,29 +111,29 @@ class Gecko(models.Model):
|
||||||
|
|
||||||
# To test fix for #11263
|
# To test fix for #11263
|
||||||
class Rock(Mineral):
|
class Rock(Mineral):
|
||||||
tags = generic.GenericRelation(TaggedItem)
|
tags = GenericRelation(TaggedItem)
|
||||||
|
|
||||||
|
|
||||||
class ManualPK(models.Model):
|
class ManualPK(models.Model):
|
||||||
id = models.IntegerField(primary_key=True)
|
id = models.IntegerField(primary_key=True)
|
||||||
tags = generic.GenericRelation(TaggedItem)
|
tags = GenericRelation(TaggedItem)
|
||||||
|
|
||||||
|
|
||||||
class ForProxyModelModel(models.Model):
|
class ForProxyModelModel(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
obj = generic.GenericForeignKey(for_concrete_model=False)
|
obj = GenericForeignKey(for_concrete_model=False)
|
||||||
title = models.CharField(max_length=255, null=True)
|
title = models.CharField(max_length=255, null=True)
|
||||||
|
|
||||||
|
|
||||||
class ForConcreteModelModel(models.Model):
|
class ForConcreteModelModel(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
obj = generic.GenericForeignKey()
|
obj = GenericForeignKey()
|
||||||
|
|
||||||
|
|
||||||
class ConcreteRelatedModel(models.Model):
|
class ConcreteRelatedModel(models.Model):
|
||||||
bases = generic.GenericRelation(ForProxyModelModel, for_concrete_model=False)
|
bases = GenericRelation(ForProxyModelModel, for_concrete_model=False)
|
||||||
|
|
||||||
|
|
||||||
class ProxyRelatedModel(ConcreteRelatedModel):
|
class ProxyRelatedModel(ConcreteRelatedModel):
|
||||||
|
@ -143,4 +145,4 @@ class ProxyRelatedModel(ConcreteRelatedModel):
|
||||||
class AllowsNullGFK(models.Model):
|
class AllowsNullGFK(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType, null=True)
|
content_type = models.ForeignKey(ContentType, null=True)
|
||||||
object_id = models.PositiveIntegerField(null=True)
|
object_id = models.PositiveIntegerField(null=True)
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.generic import generic_inlineformset_factory
|
from django.contrib.contenttypes.forms import generic_inlineformset_factory
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
@ -13,7 +15,7 @@ __all__ = ('Link', 'Place', 'Restaurant', 'Person', 'Address',
|
||||||
class Link(models.Model):
|
class Link(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Link to %s id=%s" % (self.content_type, self.object_id)
|
return "Link to %s id=%s" % (self.content_type, self.object_id)
|
||||||
|
@ -22,7 +24,7 @@ class Link(models.Model):
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Place(models.Model):
|
class Place(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
links = generic.GenericRelation(Link)
|
links = GenericRelation(Link)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Place: %s" % self.name
|
return "Place: %s" % self.name
|
||||||
|
@ -42,7 +44,7 @@ class Address(models.Model):
|
||||||
zipcode = models.CharField(max_length=5)
|
zipcode = models.CharField(max_length=5)
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s %s, %s %s' % (self.street, self.city, self.state, self.zipcode)
|
return '%s %s, %s %s' % (self.street, self.city, self.state, self.zipcode)
|
||||||
|
@ -52,7 +54,7 @@ class Address(models.Model):
|
||||||
class Person(models.Model):
|
class Person(models.Model):
|
||||||
account = models.IntegerField(primary_key=True)
|
account = models.IntegerField(primary_key=True)
|
||||||
name = models.CharField(max_length=128)
|
name = models.CharField(max_length=128)
|
||||||
addresses = generic.GenericRelation(Address)
|
addresses = GenericRelation(Address)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -61,35 +63,35 @@ class Person(models.Model):
|
||||||
class CharLink(models.Model):
|
class CharLink(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.CharField(max_length=100)
|
object_id = models.CharField(max_length=100)
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
|
|
||||||
class TextLink(models.Model):
|
class TextLink(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.TextField()
|
object_id = models.TextField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
|
|
||||||
class OddRelation1(models.Model):
|
class OddRelation1(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
clinks = generic.GenericRelation(CharLink)
|
clinks = GenericRelation(CharLink)
|
||||||
|
|
||||||
|
|
||||||
class OddRelation2(models.Model):
|
class OddRelation2(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
tlinks = generic.GenericRelation(TextLink)
|
tlinks = GenericRelation(TextLink)
|
||||||
|
|
||||||
|
|
||||||
# models for test_q_object_or:
|
# models for test_q_object_or:
|
||||||
class Note(models.Model):
|
class Note(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
note = models.TextField()
|
note = models.TextField()
|
||||||
|
|
||||||
|
|
||||||
class Contact(models.Model):
|
class Contact(models.Model):
|
||||||
notes = generic.GenericRelation(Note)
|
notes = GenericRelation(Note)
|
||||||
|
|
||||||
|
|
||||||
class Organization(models.Model):
|
class Organization(models.Model):
|
||||||
|
@ -100,7 +102,7 @@ class Organization(models.Model):
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Company(models.Model):
|
class Company(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
links = generic.GenericRelation(Link)
|
links = GenericRelation(Link)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Company: %s" % self.name
|
return "Company: %s" % self.name
|
||||||
|
@ -135,7 +137,7 @@ class Guild(models.Model):
|
||||||
class Tag(models.Model):
|
class Tag(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType, related_name='g_r_r_tags')
|
content_type = models.ForeignKey(ContentType, related_name='g_r_r_tags')
|
||||||
object_id = models.CharField(max_length=15)
|
object_id = models.CharField(max_length=15)
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
label = models.CharField(max_length=15)
|
label = models.CharField(max_length=15)
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,7 +145,7 @@ class Board(models.Model):
|
||||||
name = models.CharField(primary_key=True, max_length=15)
|
name = models.CharField(primary_key=True, max_length=15)
|
||||||
|
|
||||||
|
|
||||||
class SpecialGenericRelation(generic.GenericRelation):
|
class SpecialGenericRelation(GenericRelation):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(SpecialGenericRelation, self).__init__(*args, **kwargs)
|
super(SpecialGenericRelation, self).__init__(*args, **kwargs)
|
||||||
self.editable = True
|
self.editable = True
|
||||||
|
@ -168,11 +170,11 @@ class A(models.Model):
|
||||||
flag = models.NullBooleanField()
|
flag = models.NullBooleanField()
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = GenericForeignKey('content_type', 'object_id')
|
||||||
|
|
||||||
|
|
||||||
class B(models.Model):
|
class B(models.Model):
|
||||||
a = generic.GenericRelation(A)
|
a = GenericRelation(A)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
Various edge-cases for model managers.
|
Various edge-cases for model managers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db import models
|
from django.contrib.contenttypes.fields import (
|
||||||
from django.contrib.contenttypes import generic
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible, force_text
|
from django.utils.encoding import python_2_unicode_compatible, force_text
|
||||||
|
|
||||||
|
|
||||||
|
@ -126,7 +128,7 @@ class Child7(Parent):
|
||||||
# RelatedManagers
|
# RelatedManagers
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class RelatedModel(models.Model):
|
class RelatedModel(models.Model):
|
||||||
test_gfk = generic.GenericRelation('RelationModel', content_type_field='gfk_ctype', object_id_field='gfk_id')
|
test_gfk = GenericRelation('RelationModel', content_type_field='gfk_ctype', object_id_field='gfk_id')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return force_text(self.pk)
|
return force_text(self.pk)
|
||||||
|
@ -140,7 +142,7 @@ class RelationModel(models.Model):
|
||||||
|
|
||||||
gfk_ctype = models.ForeignKey(ContentType)
|
gfk_ctype = models.ForeignKey(ContentType)
|
||||||
gfk_id = models.IntegerField()
|
gfk_id = models.IntegerField()
|
||||||
gfk = generic.GenericForeignKey(ct_field='gfk_ctype', fk_field='gfk_id')
|
gfk = GenericForeignKey(ct_field='gfk_ctype', fk_field='gfk_id')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return force_text(self.pk)
|
return force_text(self.pk)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.contrib.contenttypes import generic
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
|
||||||
|
@ -10,7 +12,7 @@ class Review(models.Model):
|
||||||
source = models.CharField(max_length=100)
|
source = models.CharField(max_length=100)
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.source
|
return self.source
|
||||||
|
@ -57,7 +59,7 @@ class Book(models.Model):
|
||||||
published = models.DateField()
|
published = models.DateField()
|
||||||
authors = models.ManyToManyField(Person)
|
authors = models.ManyToManyField(Person)
|
||||||
editor = models.ForeignKey(Person, null=True, related_name='edited')
|
editor = models.ForeignKey(Person, null=True, related_name='edited')
|
||||||
reviews = generic.GenericRelation(Review)
|
reviews = GenericRelation(Review)
|
||||||
pages = models.IntegerField(default=100)
|
pages = models.IntegerField(default=100)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
@ -124,15 +126,15 @@ class TaggedItem(models.Model):
|
||||||
tag = models.SlugField()
|
tag = models.SlugField()
|
||||||
content_type = models.ForeignKey(ContentType, related_name="taggeditem_set2")
|
content_type = models.ForeignKey(ContentType, related_name="taggeditem_set2")
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
content_object = generic.GenericForeignKey('content_type', 'object_id')
|
content_object = GenericForeignKey('content_type', 'object_id')
|
||||||
created_by_ct = models.ForeignKey(ContentType, null=True,
|
created_by_ct = models.ForeignKey(ContentType, null=True,
|
||||||
related_name='taggeditem_set3')
|
related_name='taggeditem_set3')
|
||||||
created_by_fkey = models.PositiveIntegerField(null=True)
|
created_by_fkey = models.PositiveIntegerField(null=True)
|
||||||
created_by = generic.GenericForeignKey('created_by_ct', 'created_by_fkey',)
|
created_by = GenericForeignKey('created_by_ct', 'created_by_fkey',)
|
||||||
favorite_ct = models.ForeignKey(ContentType, null=True,
|
favorite_ct = models.ForeignKey(ContentType, null=True,
|
||||||
related_name='taggeditem_set4')
|
related_name='taggeditem_set4')
|
||||||
favorite_fkey = models.CharField(max_length=64, null=True)
|
favorite_fkey = models.CharField(max_length=64, null=True)
|
||||||
favorite = generic.GenericForeignKey('favorite_ct', 'favorite_fkey')
|
favorite = GenericForeignKey('favorite_ct', 'favorite_fkey')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.tag
|
return self.tag
|
||||||
|
@ -143,8 +145,8 @@ class TaggedItem(models.Model):
|
||||||
|
|
||||||
class Bookmark(models.Model):
|
class Bookmark(models.Model):
|
||||||
url = models.URLField()
|
url = models.URLField()
|
||||||
tags = generic.GenericRelation(TaggedItem, related_name='bookmarks')
|
tags = GenericRelation(TaggedItem, related_name='bookmarks')
|
||||||
favorite_tags = generic.GenericRelation(TaggedItem,
|
favorite_tags = GenericRelation(TaggedItem,
|
||||||
content_type_field='favorite_ct',
|
content_type_field='favorite_ct',
|
||||||
object_id_field='favorite_fkey',
|
object_id_field='favorite_fkey',
|
||||||
related_name='favorite_bookmarks')
|
related_name='favorite_bookmarks')
|
||||||
|
@ -159,7 +161,7 @@ class Comment(models.Model):
|
||||||
# Content-object field
|
# Content-object field
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_pk = models.TextField()
|
object_pk = models.TextField()
|
||||||
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
|
content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['id']
|
ordering = ['id']
|
||||||
|
|
|
@ -7,7 +7,9 @@ This class sets up a model for each model field type
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.contenttypes import generic
|
from django.contrib.contenttypes.fields import (
|
||||||
|
GenericForeignKey, GenericRelation
|
||||||
|
)
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
# The following classes are for testing basic data
|
# The following classes are for testing basic data
|
||||||
|
@ -109,7 +111,7 @@ class Tag(models.Model):
|
||||||
content_type = models.ForeignKey(ContentType)
|
content_type = models.ForeignKey(ContentType)
|
||||||
object_id = models.PositiveIntegerField()
|
object_id = models.PositiveIntegerField()
|
||||||
|
|
||||||
content_object = generic.GenericForeignKey()
|
content_object = GenericForeignKey()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["data"]
|
ordering = ["data"]
|
||||||
|
@ -118,7 +120,7 @@ class Tag(models.Model):
|
||||||
class GenericData(models.Model):
|
class GenericData(models.Model):
|
||||||
data = models.CharField(max_length=30)
|
data = models.CharField(max_length=30)
|
||||||
|
|
||||||
tags = generic.GenericRelation(Tag)
|
tags = GenericRelation(Tag)
|
||||||
|
|
||||||
# The following test classes are all for validation
|
# The following test classes are all for validation
|
||||||
# of related objects; in particular, forward, backward,
|
# of related objects; in particular, forward, backward,
|
||||||
|
|
Loading…
Reference in New Issue