From 10e3faf191d8f230dde8534d1c8fad8c8717816e Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Wed, 22 Jan 2014 01:43:33 -0500 Subject: [PATCH] Fixed #19774 -- Deprecated the contenttypes.generic module. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It contained models, forms and admin objects causing undesirable import side effects. Refs #16368. Thanks to Ramiro, Carl and Loïc for the review. --- django/contrib/comments/models.py | 4 +- django/contrib/contenttypes/admin.py | 60 ++ django/contrib/contenttypes/checks.py | 2 +- django/contrib/contenttypes/fields.py | 576 +++++++++++++++++ django/contrib/contenttypes/forms.py | 88 +++ django/contrib/contenttypes/generic.py | 732 +--------------------- docs/ref/contrib/admin/index.txt | 14 +- docs/ref/contrib/comments/models.txt | 4 +- docs/ref/contrib/contenttypes.txt | 158 +++-- docs/ref/models/querysets.txt | 4 +- docs/releases/1.4-alpha-1.txt | 3 +- docs/releases/1.4-beta-1.txt | 3 +- docs/releases/1.4.txt | 3 +- docs/releases/1.6.txt | 8 +- docs/releases/1.7.txt | 19 + tests/admin_inlines/models.py | 4 +- tests/admin_views/models.py | 8 +- tests/aggregation_regress/models.py | 8 +- tests/backends/models.py | 8 +- tests/contenttypes_tests/tests.py | 50 +- tests/custom_managers/models.py | 12 +- tests/delete_regress/models.py | 8 +- tests/fixtures/models.py | 5 +- tests/generic_inline_admin/admin.py | 12 +- tests/generic_inline_admin/models.py | 10 +- tests/generic_inline_admin/tests.py | 4 +- tests/generic_relations/models.py | 32 +- tests/generic_relations/tests.py | 2 +- tests/generic_relations_regress/models.py | 34 +- tests/managers_regress/models.py | 10 +- tests/multiple_database/models.py | 8 +- tests/prefetch_related/models.py | 16 +- tests/serializers_regress/models.py | 8 +- 33 files changed, 1011 insertions(+), 906 deletions(-) create mode 100644 django/contrib/contenttypes/admin.py create mode 100644 django/contrib/contenttypes/fields.py create mode 100644 django/contrib/contenttypes/forms.py diff --git a/django/contrib/comments/models.py b/django/contrib/comments/models.py index bc4d932464..24cd7b59cd 100644 --- a/django/contrib/comments/models.py +++ b/django/contrib/comments/models.py @@ -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) diff --git a/django/contrib/contenttypes/admin.py b/django/contrib/contenttypes/admin.py new file mode 100644 index 0000000000..c67fbca288 --- /dev/null +++ b/django/contrib/contenttypes/admin.py @@ -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' diff --git a/django/contrib/contenttypes/checks.py b/django/contrib/contenttypes/checks.py index dcd3a58b9f..890859505a 100644 --- a/django/contrib/contenttypes/checks.py +++ b/django/contrib/contenttypes/checks.py @@ -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 diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py new file mode 100644 index 0000000000..ee1e6a9060 --- /dev/null +++ b/django/contrib/contenttypes/fields.py @@ -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) diff --git a/django/contrib/contenttypes/forms.py b/django/contrib/contenttypes/forms.py new file mode 100644 index 0000000000..33df7528db --- /dev/null +++ b/django/contrib/contenttypes/forms.py @@ -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 diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index 3f9e5046ec..9da4ba1f19 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -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 +) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 281ea4674b..14d2a69d7f 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -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): diff --git a/docs/ref/contrib/comments/models.txt b/docs/ref/contrib/comments/models.txt index cae9c11971..3f9ac740bd 100644 --- a/docs/ref/contrib/comments/models.txt +++ b/docs/ref/contrib/comments/models.txt @@ -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. diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index 58a309c17d..19c2853722 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -232,7 +232,7 @@ lookup:: >>> user_type -.. 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 -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() [, ] -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:: [, ] 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 ` 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 ` and -:ref:`admin ` 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 ` and +:ref:`admin ` 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``. diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 5e310e7ac3..c063d38aa6 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -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:: diff --git a/docs/releases/1.4-alpha-1.txt b/docs/releases/1.4-alpha-1.txt index d47f624f5b..ebd0084469 100644 --- a/docs/releases/1.4-alpha-1.txt +++ b/docs/releases/1.4-alpha-1.txt @@ -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. diff --git a/docs/releases/1.4-beta-1.txt b/docs/releases/1.4-beta-1.txt index d670036463..674aa0e944 100644 --- a/docs/releases/1.4-beta-1.txt +++ b/docs/releases/1.4-beta-1.txt @@ -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. diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index f8c23728b9..e59917478a 100644 --- a/docs/releases/1.4.txt +++ b/docs/releases/1.4.txt @@ -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. diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt index bbdd81a768..8407f4f097 100644 --- a/docs/releases/1.6.txt +++ b/docs/releases/1.6.txt @@ -302,11 +302,9 @@ Minor features :class:`~django.views.generic.base.RedirectView` now support HTTP ``PATCH`` method. -* :class:`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 diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index e56939f3ce..305b34fe55 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -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`` ~~~~~~~~~~ diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py index d4374ec14b..0ad59a07bf 100644 --- a/tests/admin_inlines/models.py +++ b/tests/admin_inlines/models.py @@ -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) diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index b517530952..ac4d4f20c5 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -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 diff --git a/tests/aggregation_regress/models.py b/tests/aggregation_regress/models.py index 275e37c036..4d495b4a75 100644 --- a/tests/aggregation_regress/models.py +++ b/tests/aggregation_regress/models.py @@ -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',) diff --git a/tests/backends/models.py b/tests/backends/models.py index 1508af4354..5768960949 100644 --- a/tests/backends/models.py +++ b/tests/backends/models.py @@ -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' diff --git a/tests/contenttypes_tests/tests.py b/tests/contenttypes_tests/tests.py index 624e60d7a5..5ded3268a1 100644 --- a/tests/contenttypes_tests/tests.py +++ b/tests/contenttypes_tests/tests.py @@ -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 = [ diff --git a/tests/custom_managers/models.py b/tests/custom_managers/models.py index 238124a265..bbf037b362 100644 --- a/tests/custom_managers/models.py +++ b/tests/custom_managers/models.py @@ -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): diff --git a/tests/delete_regress/models.py b/tests/delete_regress/models.py index 033689519a..6afe84146c 100644 --- a/tests/delete_regress/models.py +++ b/tests/delete_regress/models.py @@ -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): diff --git a/tests/fixtures/models.py b/tests/fixtures/models.py index 08cb02ce72..30ffe9e8fe 100644 --- a/tests/fixtures/models.py +++ b/tests/fixtures/models.py @@ -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__, diff --git a/tests/generic_inline_admin/admin.py b/tests/generic_inline_admin/admin.py index c701e4a9eb..d0ce4c70ef 100644 --- a/tests/generic_inline_admin/admin.py +++ b/tests/generic_inline_admin/admin.py @@ -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 diff --git a/tests/generic_inline_admin/models.py b/tests/generic_inline_admin/models.py index af3a474cdf..0ae3c4b052 100644 --- a/tests/generic_inline_admin/models.py +++ b/tests/generic_inline_admin/models.py @@ -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) # diff --git a/tests/generic_inline_admin/tests.py b/tests/generic_inline_admin/tests.py index e35f0d639a..b223c98298 100644 --- a/tests/generic_inline_admin/tests.py +++ b/tests/generic_inline_admin/tests.py @@ -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 diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py index 859a17845e..436f26afb6 100644 --- a/tests/generic_relations/models.py +++ b/tests/generic_relations/models.py @@ -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() diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py index 8cd319bf03..ed12c5e92b 100644 --- a/tests/generic_relations/tests.py +++ b/tests/generic_relations/tests.py @@ -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 diff --git a/tests/generic_relations_regress/models.py b/tests/generic_relations_regress/models.py index e9fb41dfe3..a51dffb955 100644 --- a/tests/generic_relations_regress/models.py +++ b/tests/generic_relations_regress/models.py @@ -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',) diff --git a/tests/managers_regress/models.py b/tests/managers_regress/models.py index 80b25a72fc..c8869f1e26 100644 --- a/tests/managers_regress/models.py +++ b/tests/managers_regress/models.py @@ -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) diff --git a/tests/multiple_database/models.py b/tests/multiple_database/models.py index fc5b28ad92..e02bb17f27 100644 --- a/tests/multiple_database/models.py +++ b/tests/multiple_database/models.py @@ -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): diff --git a/tests/prefetch_related/models.py b/tests/prefetch_related/models.py index e1b844dde5..35be3d6804 100644 --- a/tests/prefetch_related/models.py +++ b/tests/prefetch_related/models.py @@ -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'] diff --git a/tests/serializers_regress/models.py b/tests/serializers_regress/models.py index 2c4a43123e..2f8d0bf0e8 100644 --- a/tests/serializers_regress/models.py +++ b/tests/serializers_regress/models.py @@ -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,