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:
Simon Charette 2014-01-22 01:43:33 -05:00
parent c3881944e8
commit 10e3faf191
33 changed files with 1011 additions and 906 deletions

View File

@ -1,6 +1,6 @@
from django.conf import settings
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.sites.models import Site
from django.core import urlresolvers
@ -23,7 +23,7 @@ class BaseCommentAbstractModel(models.Model):
verbose_name=_('content type'),
related_name="content_type_set_for_%(class)s")
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
site = models.ForeignKey(Site)

View File

@ -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'

View File

@ -6,7 +6,7 @@ from django.apps import apps
def check_generic_foreign_keys(**kwargs):
from .generic import GenericForeignKey
from .fields import GenericForeignKey
errors = []
fields = (obj

View File

@ -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)

View File

@ -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

View File

@ -1,716 +1,20 @@
"""
Classes allowing "generic" relations through ContentType and object-id fields.
"""
from __future__ import unicode_literals
from collections import defaultdict
from functools import partial
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.forms import ModelForm, ALL_FIELDS
from django.forms.models import (BaseModelFormSet, modelformset_factory,
modelform_defines_fields)
from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets
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)
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'
import warnings
warnings.warn(
('django.contrib.contenttypes.generic is deprecated and will be removed in '
'Django 1.9. Its contents have been moved to the fields, forms, and admin '
'submodules of django.contrib.contenttypes.'), PendingDeprecationWarning, stacklevel=2
)
from django.contrib.contenttypes.admin import ( # NOQA
GenericInlineModelAdmin, GenericStackedInline, GenericTabularInline
)
from django.contrib.contenttypes.fields import ( # NOQA
GenericForeignKey, GenericRelation
)
from django.contrib.contenttypes.forms import ( # NOQA
BaseGenericInlineFormSet, generic_inlineformset_factory
)

View File

@ -2187,30 +2187,32 @@ It is possible to use an inline with generically related objects. Let's say
you have the following models::
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
class Image(models.Model):
image = models.ImageField(upload_to="images")
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey("content_type", "object_id")
content_object = GenericForeignKey("content_type", "object_id")
class Product(models.Model):
name = models.CharField(max_length=100)
If you want to allow editing and creating ``Image`` instance on the ``Product``
add/change views you can use ``GenericTabularInline`` or
``GenericStackedInline`` (both subclasses of ``GenericInlineModelAdmin``)
provided by ``django.contrib.contenttypes.generic``, they implement tabular and
add/change views you can use :class:`~django.contrib.contenttypes.admin.GenericTabularInline`
or :class:`~django.contrib.contenttypes.admin.GenericStackedInline` (both
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
respectively just like their non-generic counterparts and behave just like any
other inline. In your ``admin.py`` for this example app::
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
class ImageInline(generic.GenericTabularInline):
class ImageInline(GenericTabularInline):
model = Image
class ProductAdmin(admin.ModelAdmin):

View File

@ -23,12 +23,12 @@ The built-in comment models
.. 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
this to get at the related object (i.e. ``my_comment.content_object``).
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
below.

View File

@ -232,7 +232,7 @@ lookup::
>>> user_type
<ContentType: user>
.. module:: django.contrib.contenttypes.generic
.. module:: django.contrib.contenttypes.fields
.. _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::
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType)
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):
def __unicode__(self):
@ -274,7 +274,7 @@ model:
.. class:: GenericForeignKey
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`
to :class:`~django.contrib.contenttypes.models.ContentType`. The usual
@ -286,11 +286,11 @@ model:
for this field is "object_id".
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
are named "content_type" and "object_id", you can omit this -- those
are the default field names
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` will
:class:`~django.contrib.contenttypes.fields.GenericForeignKey` will
look for.
.. attribute:: GenericForeignKey.for_concrete_model
@ -301,6 +301,10 @@ model:
is ``True``. This mirrors the ``for_concrete_model`` argument to
: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
@ -347,10 +351,10 @@ creating a ``TaggedItem``::
>>> t.content_object
<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()``
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::
# This will fail
@ -358,7 +362,7 @@ normal field object, these examples will *not* work::
# This will also fail
>>> 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.
Reverse generic relations
@ -366,12 +370,16 @@ Reverse generic relations
.. 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
a "reverse" generic relationship to enable an additional API. For example::
class Bookmark(models.Model):
url = models.URLField()
tags = generic.GenericRelation(TaggedItem)
tags = GenericRelation(TaggedItem)
``Bookmark`` instances will each have a ``tags`` attribute, which can
be used to retrieve their associated ``TaggedItems``::
@ -385,10 +393,10 @@ be used to retrieve their associated ``TaggedItems``::
>>> b.tags.all()
[<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
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
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
@ -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
:class:`.GenericRelation` back to it would need to be defined like so::
tags = generic.GenericRelation(TaggedItem,
content_type_field='content_type_fk',
object_id_field='object_primary_key')
tags = GenericRelation(TaggedItem,
content_type_field='content_type_fk',
object_id_field='object_primary_key')
Of course, if you don't add the reverse relationship, you can do the
same types of lookups manually::
@ -410,29 +418,29 @@ same types of lookups manually::
[<TaggedItem: django>, <TaggedItem: python>]
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
: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"``),
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
: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
:class:`~django.contrib.contenttypes.generic.GenericRelation`, any objects
which have a :class:`~django.contrib.contenttypes.generic.GenericForeignKey`
:class:`~django.contrib.contenttypes.fields.GenericRelation`, any objects
which have a :class:`~django.contrib.contenttypes.fields.GenericForeignKey`
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
it would be deleted at the same time.
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
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`
signal.
@ -441,7 +449,7 @@ Generic relations and aggregation
:doc:`Django's database aggregation API </topics/db/aggregation>`
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::
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
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``
* :class:`~django.contrib.contenttypes.generic.GenericTabularInline`
and :class:`~django.contrib.contenttypes.generic.GenericStackedInline`
(subclasses of
:class:`~django.contrib.contenttypes.generic.GenericInlineModelAdmin`)
Generic relation in forms
-------------------------
The :mod:`django.contrib.contenttypes.forms` module provides:
* :class:`BaseGenericInlineFormSet`
* 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
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:: BaseGenericInlineFormSet
.. class:: GenericInlineModelAdmin
.. versionchanged:: 1.7
The :class:`~django.contrib.contenttypes.generic.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``.
.. class:: GenericTabularInline
.. class:: GenericStackedInline
Subclasses of :class:`GenericInlineModelAdmin` with stacked and tabular
layouts, respectively.
This class used to be defined in ``django.contrib.contenttypes.generic``.
.. 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
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``.
.. 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``.

View File

@ -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
``select_related``, in addition to the foreign key and one-to-one relationships
that are supported by ``select_related``. It also supports prefetching of
:class:`~django.contrib.contenttypes.generic.GenericRelation` and
:class:`~django.contrib.contenttypes.generic.GenericForeignKey`.
:class:`~django.contrib.contenttypes.fields.GenericRelation` and
:class:`~django.contrib.contenttypes.fields.GenericForeignKey`.
For example, suppose you have these models::

View File

@ -92,8 +92,7 @@ different strategy and broader scope,
``QuerySet`` that will prefetch each of the specified related lookups in a
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
supports many-to-many relationships,
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` and more. This
supports many-to-many relationships, ``GenericForeignKey`` and more. This
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``
each have many related objects that you also need.

View File

@ -108,8 +108,7 @@ different strategy and broader scope,
``QuerySet`` that will prefetch each of the specified related lookups in a
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
supports many-to-many relationships,
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` and more. This
supports many-to-many relationships, ``GenericForeignKey`` and more. This
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``
each have many related objects that you also need.

View File

@ -257,8 +257,7 @@ different strategy and broader scope,
``QuerySet`` that will prefetch each of the specified related lookups in a
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
supports many-to-many relationships,
:class:`~django.contrib.contenttypes.generic.GenericForeignKey` and more. This
supports many-to-many relationships, ``GenericForeignKey`` and more. This
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``
each have many related objects that you also need to fetch.

View File

@ -302,11 +302,9 @@ Minor features
:class:`~django.views.generic.base.RedirectView` now support HTTP ``PATCH``
method.
* :class:`GenericForeignKey <django.contrib.contenttypes.generic.GenericForeignKey>`
now takes an optional
:attr:`~django.contrib.contenttypes.generic.GenericForeignKey.for_concrete_model`
argument, which when set to ``False`` allows the field to reference proxy
models. The default is ``True`` to retain the old behavior.
* ``GenericForeignKey`` now takes an optional ``for_concrete_model`` 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
language in session if it is not present there. This prevents loss of

View File

@ -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
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``
~~~~~~~~~~

View File

@ -6,8 +6,8 @@ from __future__ import unicode_literals
import random
from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.utils.encoding import python_2_unicode_compatible
@ -34,7 +34,7 @@ class Child(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
parent = generic.GenericForeignKey()
parent = GenericForeignKey()
def __str__(self):
return 'I am %s, a child of %s' % (self.name, self.parent)

View File

@ -6,7 +6,9 @@ import tempfile
import os
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.core.files.storage import FileSystemStorage
from django.db import models
@ -430,7 +432,7 @@ class FunkyTag(models.Model):
name = models.CharField(max_length=25)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return self.name
@ -441,7 +443,7 @@ class Plot(models.Model):
name = models.CharField(max_length=100)
team_leader = models.ForeignKey(Villain, related_name='lead_plots')
contact = models.ForeignKey(Villain, related_name='contact_plots')
tags = generic.GenericRelation(FunkyTag)
tags = GenericRelation(FunkyTag)
def __str__(self):
return self.name

View File

@ -1,5 +1,7 @@
# 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.db import models
from django.utils.encoding import python_2_unicode_compatible
@ -28,7 +30,7 @@ class ItemTag(models.Model):
tag = models.CharField(max_length=100)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
content_object = GenericForeignKey('content_type', 'object_id')
@python_2_unicode_compatible
@ -42,7 +44,7 @@ class Book(models.Model):
contact = models.ForeignKey(Author, related_name='book_contact_set')
publisher = models.ForeignKey(Publisher)
pubdate = models.DateField()
tags = generic.GenericRelation(ItemTag)
tags = GenericRelation(ItemTag)
class Meta:
ordering = ('name',)

View File

@ -1,6 +1,8 @@
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.db import models, connection
from django.utils.encoding import python_2_unicode_compatible
@ -46,13 +48,13 @@ class Tag(models.Model):
name = models.CharField(max_length=30)
content_type = models.ForeignKey(ContentType, related_name='backend_tags')
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
content_object = GenericForeignKey('content_type', 'object_id')
class Post(models.Model):
name = models.CharField(max_length=30)
text = models.TextField()
tags = generic.GenericRelation('Tag')
tags = GenericRelation('Tag')
class Meta:
db_table = 'CaseSensitive_Post'

View File

@ -2,7 +2,9 @@
from __future__ import absolute_import, unicode_literals
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.core import checks
from django.db import models
@ -90,7 +92,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
def test_str(self):
class Model(models.Model):
field = generic.GenericForeignKey()
field = GenericForeignKey()
expected = "contenttypes_tests.Model.field"
actual = force_str(Model.field)
self.assertEqual(expected, actual)
@ -99,7 +101,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
class TaggedItem(models.Model):
# no content_type field
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
errors = TaggedItem.content_object.check()
expected = [
@ -116,7 +118,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
class Model(models.Model):
content_type = models.IntegerField() # should be ForeignKey
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey(
content_object = GenericForeignKey(
'content_type', 'object_id')
errors = Model.content_object.check()
@ -136,7 +138,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
class Model(models.Model):
content_type = models.ForeignKey('self') # should point to ContentType
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey(
content_object = GenericForeignKey(
'content_type', 'object_id')
errors = Model.content_object.check()
@ -156,7 +158,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType)
# missing object_id field
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
errors = TaggedItem.content_object.check()
expected = [
@ -173,7 +175,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
class Model(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object_ = generic.GenericForeignKey(
content_object_ = GenericForeignKey(
'content_type', 'object_id')
errors = Model.content_object_.check()
@ -188,7 +190,7 @@ class GenericForeignKeyTests(IsolatedModelsTestCase):
self.assertEqual(errors, expected)
def test_generic_foreign_key_checks_are_performed(self):
class MyGenericForeignKey(generic.GenericForeignKey):
class MyGenericForeignKey(GenericForeignKey):
def check(self, **kwargs):
return ['performed!']
@ -205,10 +207,10 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
class Bookmark(models.Model):
tags = generic.GenericRelation('TaggedItem')
tags = GenericRelation('TaggedItem')
errors = Bookmark.tags.field.check()
self.assertEqual(errors, [])
@ -217,11 +219,11 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
class TaggedItem(models.Model):
custom_content_type = models.ForeignKey(ContentType)
custom_object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey(
content_object = GenericForeignKey(
'custom_content_type', 'custom_object_id')
class Bookmark(models.Model):
tags = generic.GenericRelation('TaggedItem',
tags = GenericRelation('TaggedItem',
content_type_field='custom_content_type',
object_id_field='custom_object_id')
@ -230,7 +232,7 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
def test_pointing_to_missing_model(self):
class Model(models.Model):
rel = generic.GenericRelation('MissingModel')
rel = GenericRelation('MissingModel')
errors = Model.rel.field.check()
expected = [
@ -248,10 +250,10 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
def test_valid_self_referential_generic_relationship(self):
class Model(models.Model):
rel = generic.GenericRelation('Model')
rel = GenericRelation('Model')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey(
content_object = GenericForeignKey(
'content_type', 'object_id')
errors = Model.rel.field.check()
@ -261,10 +263,10 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
class TaggedItem(models.Model):
# no content_type field
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
class Bookmark(models.Model):
tags = generic.GenericRelation('TaggedItem')
tags = GenericRelation('TaggedItem')
errors = Bookmark.tags.field.check()
expected = [
@ -281,10 +283,10 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType)
# missing object_id field
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
class Bookmark(models.Model):
tags = generic.GenericRelation('TaggedItem')
tags = GenericRelation('TaggedItem')
errors = Bookmark.tags.field.check()
expected = [
@ -303,7 +305,7 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
object_id = models.PositiveIntegerField()
class Bookmark(models.Model):
tags = generic.GenericRelation('TaggedItem')
tags = GenericRelation('TaggedItem')
errors = Bookmark.tags.field.check()
expected = [
@ -326,13 +328,13 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
class SwappedModel(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
class Meta:
swappable = 'TEST_SWAPPED_MODEL'
class Model(models.Model):
rel = generic.GenericRelation('SwappedModel')
rel = GenericRelation('SwappedModel')
errors = Model.rel.field.check()
expected = [
@ -351,10 +353,10 @@ class GenericRelationshipTests(IsolatedModelsTestCase):
class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
class InvalidBookmark(models.Model):
tags_ = generic.GenericRelation('TaggedItem')
tags_ = GenericRelation('TaggedItem')
errors = InvalidBookmark.tags_.field.check()
expected = [

View File

@ -11,7 +11,9 @@ returns.
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.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_thing_type = models.ForeignKey('contenttypes.ContentType', 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()
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_thing_type = models.ForeignKey('contenttypes.ContentType', 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()
@ -127,10 +129,10 @@ class Book(models.Model):
authors = models.ManyToManyField(Person, 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')
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')
def __str__(self):

View File

@ -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.db import models
@ -7,7 +9,7 @@ class Award(models.Model):
name = models.CharField(max_length=25)
object_id = models.PositiveIntegerField()
content_type = models.ForeignKey(ContentType)
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
class AwardNote(models.Model):
@ -17,7 +19,7 @@ class AwardNote(models.Model):
class Person(models.Model):
name = models.CharField(max_length=25)
awards = generic.GenericRelation(Award)
awards = GenericRelation(Award)
class Book(models.Model):

View File

@ -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.contenttypes import generic
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@ -55,8 +55,7 @@ class Tag(models.Model):
name = models.CharField(max_length=100)
tagged_type = models.ForeignKey(ContentType, related_name="fixtures_tag_set")
tagged_id = models.PositiveIntegerField(default=0)
tagged = generic.GenericForeignKey(ct_field='tagged_type',
fk_field='tagged_id')
tagged = GenericForeignKey(ct_field='tagged_type', fk_field='tagged_id')
def __str__(self):
return '<%s: %s> tagged "%s"' % (self.tagged.__class__.__name__,

View File

@ -1,5 +1,5 @@
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,
Category, EpisodePermanent, EpisodeMaxNum)
@ -8,7 +8,7 @@ from .models import (Media, PhoneNumber, Episode, EpisodeExtra, Contact,
site = admin.AdminSite(name="admin")
class MediaInline(generic.GenericTabularInline):
class MediaInline(GenericTabularInline):
model = Media
@ -18,22 +18,22 @@ class EpisodeAdmin(admin.ModelAdmin):
]
class MediaExtraInline(generic.GenericTabularInline):
class MediaExtraInline(GenericTabularInline):
model = Media
extra = 0
class MediaMaxNumInline(generic.GenericTabularInline):
class MediaMaxNumInline(GenericTabularInline):
model = Media
extra = 5
max_num = 2
class PhoneNumberInline(generic.GenericTabularInline):
class PhoneNumberInline(GenericTabularInline):
model = PhoneNumber
class MediaPermanentInline(generic.GenericTabularInline):
class MediaPermanentInline(GenericTabularInline):
model = Media
can_delete = False

View File

@ -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.db import models
from django.utils.encoding import python_2_unicode_compatible
@ -17,7 +19,7 @@ class Media(models.Model):
"""
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
url = models.URLField()
description = 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):
content_type = models.ForeignKey(ContentType)
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)
category = models.ForeignKey(Category, null=True, blank=True)
@ -66,7 +68,7 @@ class PhoneNumber(models.Model):
class Contact(models.Model):
name = models.CharField(max_length=50)
phone_numbers = generic.GenericRelation(PhoneNumber)
phone_numbers = GenericRelation(PhoneNumber)
#

View File

@ -4,8 +4,8 @@ import warnings
from django.contrib import admin
from django.contrib.admin.sites import AdminSite
from django.contrib.contenttypes.generic import (
generic_inlineformset_factory, GenericTabularInline)
from django.contrib.contenttypes.admin import GenericTabularInline
from django.contrib.contenttypes.forms import generic_inlineformset_factory
from django.forms.formsets import DEFAULT_MAX_NUM
from django.forms.models import ModelForm
from django.test import TestCase, override_settings

View File

@ -11,7 +11,9 @@ from complete).
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.db import models
from django.utils.encoding import python_2_unicode_compatible
@ -24,7 +26,7 @@ class TaggedItem(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
class Meta:
ordering = ["tag", "content_type__name"]
@ -43,7 +45,7 @@ class AbstractComparison(models.Model):
content_type1 = models.ForeignKey(ContentType, related_name="comparative1_set")
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
@ -55,7 +57,7 @@ class Comparison(AbstractComparison):
content_type2 = models.ForeignKey(ContentType, related_name="comparative2_set")
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):
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)
latin_name = models.CharField(max_length=150)
tags = generic.GenericRelation(TaggedItem)
comparisons = generic.GenericRelation(Comparison,
object_id_field="object_id1",
content_type_field="content_type1")
tags = GenericRelation(TaggedItem)
comparisons = GenericRelation(Comparison,
object_id_field="object_id1",
content_type_field="content_type1")
def __str__(self):
return self.common_name
@ -80,7 +82,7 @@ class Vegetable(models.Model):
name = models.CharField(max_length=150)
is_yucky = models.BooleanField(default=True)
tags = generic.GenericRelation(TaggedItem)
tags = GenericRelation(TaggedItem)
def __str__(self):
return self.name
@ -109,29 +111,29 @@ class Gecko(models.Model):
# To test fix for #11263
class Rock(Mineral):
tags = generic.GenericRelation(TaggedItem)
tags = GenericRelation(TaggedItem)
class ManualPK(models.Model):
id = models.IntegerField(primary_key=True)
tags = generic.GenericRelation(TaggedItem)
tags = GenericRelation(TaggedItem)
class ForProxyModelModel(models.Model):
content_type = models.ForeignKey(ContentType)
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)
class ForConcreteModelModel(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
obj = generic.GenericForeignKey()
obj = GenericForeignKey()
class ConcreteRelatedModel(models.Model):
bases = generic.GenericRelation(ForProxyModelModel, for_concrete_model=False)
bases = GenericRelation(ForProxyModelModel, for_concrete_model=False)
class ProxyRelatedModel(ConcreteRelatedModel):
@ -143,4 +145,4 @@ class ProxyRelatedModel(ConcreteRelatedModel):
class AllowsNullGFK(models.Model):
content_type = models.ForeignKey(ContentType, null=True)
object_id = models.PositiveIntegerField(null=True)
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals
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.test import TestCase
from django.utils import six

View File

@ -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.db import models
from django.utils.encoding import python_2_unicode_compatible
@ -13,7 +15,7 @@ __all__ = ('Link', 'Place', 'Restaurant', 'Person', 'Address',
class Link(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
def __str__(self):
return "Link to %s id=%s" % (self.content_type, self.object_id)
@ -22,7 +24,7 @@ class Link(models.Model):
@python_2_unicode_compatible
class Place(models.Model):
name = models.CharField(max_length=100)
links = generic.GenericRelation(Link)
links = GenericRelation(Link)
def __str__(self):
return "Place: %s" % self.name
@ -42,7 +44,7 @@ class Address(models.Model):
zipcode = models.CharField(max_length=5)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
def __str__(self):
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):
account = models.IntegerField(primary_key=True)
name = models.CharField(max_length=128)
addresses = generic.GenericRelation(Address)
addresses = GenericRelation(Address)
def __str__(self):
return self.name
@ -61,35 +63,35 @@ class Person(models.Model):
class CharLink(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.CharField(max_length=100)
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
class TextLink(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.TextField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
class OddRelation1(models.Model):
name = models.CharField(max_length=100)
clinks = generic.GenericRelation(CharLink)
clinks = GenericRelation(CharLink)
class OddRelation2(models.Model):
name = models.CharField(max_length=100)
tlinks = generic.GenericRelation(TextLink)
tlinks = GenericRelation(TextLink)
# models for test_q_object_or:
class Note(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
note = models.TextField()
class Contact(models.Model):
notes = generic.GenericRelation(Note)
notes = GenericRelation(Note)
class Organization(models.Model):
@ -100,7 +102,7 @@ class Organization(models.Model):
@python_2_unicode_compatible
class Company(models.Model):
name = models.CharField(max_length=100)
links = generic.GenericRelation(Link)
links = GenericRelation(Link)
def __str__(self):
return "Company: %s" % self.name
@ -135,7 +137,7 @@ class Guild(models.Model):
class Tag(models.Model):
content_type = models.ForeignKey(ContentType, related_name='g_r_r_tags')
object_id = models.CharField(max_length=15)
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
label = models.CharField(max_length=15)
@ -143,7 +145,7 @@ class Board(models.Model):
name = models.CharField(primary_key=True, max_length=15)
class SpecialGenericRelation(generic.GenericRelation):
class SpecialGenericRelation(GenericRelation):
def __init__(self, *args, **kwargs):
super(SpecialGenericRelation, self).__init__(*args, **kwargs)
self.editable = True
@ -168,11 +170,11 @@ class A(models.Model):
flag = models.NullBooleanField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
content_object = GenericForeignKey('content_type', 'object_id')
class B(models.Model):
a = generic.GenericRelation(A)
a = GenericRelation(A)
class Meta:
ordering = ('id',)

View File

@ -2,9 +2,11 @@
Various edge-cases for model managers.
"""
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.db import models
from django.utils.encoding import python_2_unicode_compatible, force_text
@ -126,7 +128,7 @@ class Child7(Parent):
# RelatedManagers
@python_2_unicode_compatible
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):
return force_text(self.pk)
@ -140,7 +142,7 @@ class RelationModel(models.Model):
gfk_ctype = models.ForeignKey(ContentType)
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):
return force_text(self.pk)

View File

@ -1,6 +1,8 @@
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 import generic
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@ -10,7 +12,7 @@ class Review(models.Model):
source = models.CharField(max_length=100)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
def __str__(self):
return self.source
@ -57,7 +59,7 @@ class Book(models.Model):
published = models.DateField()
authors = models.ManyToManyField(Person)
editor = models.ForeignKey(Person, null=True, related_name='edited')
reviews = generic.GenericRelation(Review)
reviews = GenericRelation(Review)
pages = models.IntegerField(default=100)
def __str__(self):

View File

@ -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.db import models
from django.utils.encoding import python_2_unicode_compatible
@ -124,15 +126,15 @@ class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, related_name="taggeditem_set2")
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,
related_name='taggeditem_set3')
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,
related_name='taggeditem_set4')
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):
return self.tag
@ -143,8 +145,8 @@ class TaggedItem(models.Model):
class Bookmark(models.Model):
url = models.URLField()
tags = generic.GenericRelation(TaggedItem, related_name='bookmarks')
favorite_tags = generic.GenericRelation(TaggedItem,
tags = GenericRelation(TaggedItem, related_name='bookmarks')
favorite_tags = GenericRelation(TaggedItem,
content_type_field='favorite_ct',
object_id_field='favorite_fkey',
related_name='favorite_bookmarks')
@ -159,7 +161,7 @@ class Comment(models.Model):
# Content-object field
content_type = models.ForeignKey(ContentType)
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:
ordering = ['id']

View File

@ -7,7 +7,9 @@ This class sets up a model for each model field type
import warnings
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
# The following classes are for testing basic data
@ -109,7 +111,7 @@ class Tag(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
content_object = GenericForeignKey()
class Meta:
ordering = ["data"]
@ -118,7 +120,7 @@ class Tag(models.Model):
class GenericData(models.Model):
data = models.CharField(max_length=30)
tags = generic.GenericRelation(Tag)
tags = GenericRelation(Tag)
# The following test classes are all for validation
# of related objects; in particular, forward, backward,