diff --git a/django/contrib/admin/checks.py b/django/contrib/admin/checks.py index d8f7fe57e43..8788fd108d8 100644 --- a/django/contrib/admin/checks.py +++ b/django/contrib/admin/checks.py @@ -180,7 +180,7 @@ class BaseModelAdminChecks(object): return [] else: if (isinstance(field, models.ManyToManyField) and - not field.rel.through._meta.auto_created): + not field.remote_field.through._meta.auto_created): return [ checks.Error( ("The value of '%s' cannot include the ManyToManyField '%s', " diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index 863da768181..80ba2eebc6e 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -9,12 +9,10 @@ import datetime from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.utils import ( - get_limit_choices_to_from_path, get_model_from_relation, - prepare_lookup_value, reverse_field_path, + get_model_from_relation, prepare_lookup_value, reverse_field_path, ) from django.core.exceptions import ImproperlyConfigured, ValidationError from django.db import models -from django.db.models.fields.related import ForeignObjectRel, ManyToManyField from django.utils import timezone from django.utils.encoding import force_text, smart_text from django.utils.translation import ugettext_lazy as _ @@ -164,11 +162,7 @@ class FieldListFilter(ListFilter): class RelatedFieldListFilter(FieldListFilter): def __init__(self, field, request, params, model, model_admin, field_path): other_model = get_model_from_relation(field) - if hasattr(field, 'rel'): - rel_name = field.rel.get_related_field().name - else: - rel_name = other_model._meta.pk.name - self.lookup_kwarg = '%s__%s__exact' % (field_path, rel_name) + self.lookup_kwarg = '%s__%s__exact' % (field_path, field.target_field.name) self.lookup_kwarg_isnull = '%s__isnull' % field_path self.lookup_val = request.GET.get(self.lookup_kwarg) self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull) @@ -182,9 +176,7 @@ class RelatedFieldListFilter(FieldListFilter): self.title = self.lookup_title def has_output(self): - if (isinstance(self.field, ForeignObjectRel) and - self.field.field.null or hasattr(self.field, 'rel') and - self.field.null): + if self.field.null: extra = 1 else: extra = 0 @@ -212,9 +204,7 @@ class RelatedFieldListFilter(FieldListFilter): }, [self.lookup_kwarg_isnull]), 'display': val, } - if (isinstance(self.field, ForeignObjectRel) and - (self.field.field.null or isinstance(self.field.field, ManyToManyField)) or - hasattr(self.field, 'rel') and (self.field.null or isinstance(self.field, ManyToManyField))): + if self.field.null: yield { 'selected': bool(self.lookup_val_isnull), 'query_string': cl.get_query_string({ @@ -223,9 +213,7 @@ class RelatedFieldListFilter(FieldListFilter): 'display': EMPTY_CHANGELIST_VALUE, } -FieldListFilter.register(lambda f: ( - bool(f.rel) if hasattr(f, 'rel') else - isinstance(f, ForeignObjectRel)), RelatedFieldListFilter) +FieldListFilter.register(lambda f: f.remote_field, RelatedFieldListFilter) class BooleanFieldListFilter(FieldListFilter): @@ -371,13 +359,6 @@ class AllValuesFieldListFilter(FieldListFilter): queryset = model_admin.get_queryset(request) else: queryset = parent_model._default_manager.all() - - # optional feature: limit choices base on existing relationships - # queryset = queryset.complex_filter( - # {'%s__isnull' % reverse_path: False}) - limit_choices_to = get_limit_choices_to_from_path(model, field_path) - queryset = queryset.filter(limit_choices_to) - self.lookup_choices = (queryset .distinct() .order_by(field.name) diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 07ea9a48139..b44d4118285 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -203,7 +203,7 @@ class AdminReadonlyField(object): else: result_repr = linebreaksbr(result_repr) else: - if isinstance(f.rel, ManyToManyRel) and value is not None: + if isinstance(f.remote_field, ManyToManyRel) and value is not None: result_repr = ", ".join(map(six.text_type, value.all())) else: result_repr = display_for_field(value, f) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 855f605d134..b85f0fdeac4 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -26,8 +26,6 @@ from django.core.urlresolvers import reverse from django.db import models, router, transaction from django.db.models.constants import LOOKUP_SEP from django.db.models.fields import BLANK_CHOICE_DASH -from django.db.models.fields.related import ForeignObjectRel -from django.db.models.sql.constants import QUERY_TERMS from django.forms.formsets import DELETION_FIELD_NAME, all_valid from django.forms.models import ( BaseInlineFormSet, inlineformset_factory, modelform_defines_fields, @@ -154,7 +152,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): # rendered output. formfield can be None if it came from a # OneToOneField with parent_link=True or a M2M intermediary. if formfield and db_field.name not in self.raw_id_fields: - related_modeladmin = self.admin_site._registry.get(db_field.rel.to) + related_modeladmin = self.admin_site._registry.get(db_field.remote_field.model) wrapper_kwargs = {} if related_modeladmin: wrapper_kwargs.update( @@ -163,7 +161,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): can_delete_related=related_modeladmin.has_delete_permission(request), ) formfield.widget = widgets.RelatedFieldWidgetWrapper( - formfield.widget, db_field.rel, self.admin_site, **wrapper_kwargs + formfield.widget, db_field.remote_field, self.admin_site, **wrapper_kwargs ) return formfield @@ -202,11 +200,11 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): ordering. Otherwise don't specify the queryset, let the field decide (returns None in that case). """ - related_admin = self.admin_site._registry.get(db_field.rel.to, None) + related_admin = self.admin_site._registry.get(db_field.remote_field.model, None) if related_admin is not None: ordering = related_admin.get_ordering(request) if ordering is not None and ordering != (): - return db_field.rel.to._default_manager.using(db).order_by(*ordering) + return db_field.remote_field.model._default_manager.using(db).order_by(*ordering) return None def formfield_for_foreignkey(self, db_field, request=None, **kwargs): @@ -215,7 +213,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): """ db = kwargs.get('using') if db_field.name in self.raw_id_fields: - kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, + kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.remote_field, self.admin_site, using=db) elif db_field.name in self.radio_fields: kwargs['widget'] = widgets.AdminRadioSelect(attrs={ @@ -236,12 +234,12 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): """ # If it uses an intermediary model that isn't auto created, don't show # a field in admin. - if not db_field.rel.through._meta.auto_created: + if not db_field.remote_field.through._meta.auto_created: return None db = kwargs.get('using') if db_field.name in self.raw_id_fields: - kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, + kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.remote_field, self.admin_site, using=db) kwargs['help_text'] = '' elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)): @@ -334,46 +332,32 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): if k == lookup and v == value: return True - parts = lookup.split(LOOKUP_SEP) - - # Last term in lookup is a query term (__exact, __startswith etc) - # This term can be ignored. - if len(parts) > 1 and parts[-1] in QUERY_TERMS: - parts.pop() - - # Special case -- foo__id__exact and foo__id queries are implied - # if foo has been specifically included in the lookup list; so - # drop __id if it is the last part. However, first we need to find - # the pk attribute name. - rel_name = None - for part in parts[:-1]: + relation_parts = [] + prev_field = None + for part in lookup.split(LOOKUP_SEP): try: field = model._meta.get_field(part) except FieldDoesNotExist: # Lookups on non-existent fields are ok, since they're ignored # later. - return True - if hasattr(field, 'rel'): - if field.rel is None: - # This property or relation doesn't exist, but it's allowed - # since it's ignored in ChangeList.get_filters(). - return True - model = field.rel.to - if hasattr(field.rel, 'get_related_field'): - rel_name = field.rel.get_related_field().name - else: - rel_name = None - elif isinstance(field, ForeignObjectRel): - model = field.related_model - rel_name = model._meta.pk.name - else: - rel_name = None - if rel_name and len(parts) > 1 and parts[-1] == rel_name: - parts.pop() + break + # It is allowed to filter on values that would be found from local + # model anyways. For example, if you filter on employee__department__id, + # then the id value would be found already from employee__department_id. + if not prev_field or (prev_field.concrete and + field not in prev_field.get_path_info()[-1].target_fields): + relation_parts.append(part) + if not getattr(field, 'get_path_info', None): + # This is not a relational field, so further parts + # must be transforms. + break + prev_field = field + model = field.get_path_info()[-1].to_opts.model - if len(parts) == 1: + if len(relation_parts) <= 1: + # Either a local field filter, or no fields at all. return True - clean_lookup = LOOKUP_SEP.join(parts) + clean_lookup = LOOKUP_SEP.join(relation_parts) valid_lookups = [self.date_hierarchy] for filter_item in self.list_filter: if isinstance(filter_item, type) and issubclass(filter_item, SimpleListFilter): @@ -422,7 +406,7 @@ class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)): for related_object in related_objects: related_model = related_object.related_model if (any(issubclass(model, related_model) for model in registered_models) and - related_object.field.rel.get_related_field() == field): + related_object.field.remote_field.get_related_field() == field): return True return False @@ -1882,8 +1866,8 @@ class InlineModelAdmin(BaseModelAdmin): # The model was auto-created as intermediary for a # ManyToMany-relationship, find the target model for field in opts.fields: - if field.rel and field.rel.to != self.parent_model: - opts = field.rel.to._meta + if field.remote_field and field.remote_field.model != self.parent_model: + opts = field.remote_field.model._meta break codename = get_permission_codename('change', opts) return request.user.has_perm("%s.%s" % (opts.app_label, codename)) diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 55fba4f90ce..affd5958009 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -215,7 +215,7 @@ def items_for_result(cl, result, form): if isinstance(value, (datetime.date, datetime.time)): row_classes.append('nowrap') else: - if isinstance(f.rel, models.ManyToOneRel): + if isinstance(f.remote_field, models.ManyToOneRel): field_val = getattr(result, f.name) if field_val is None: result_repr = EMPTY_CHANGELIST_VALUE diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index 619e9ad796c..69465fa9272 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -446,7 +446,7 @@ def reverse_field_path(model, path): # Field should point to another model if field.is_relation and not (field.auto_created and not field.concrete): related_name = field.related_query_name() - parent = field.rel.to + parent = field.remote_field.model else: related_name = field.field.name parent = field.related_model @@ -481,23 +481,3 @@ def remove_trailing_data_field(fields): except NotRelationField: fields = fields[:-1] return fields - - -def get_limit_choices_to_from_path(model, path): - """ Return Q object for limiting choices if applicable. - - If final model in path is linked via a ForeignKey or ManyToManyField which - has a ``limit_choices_to`` attribute, return it as a Q object. - """ - fields = get_fields_from_path(model, path) - fields = remove_trailing_data_field(fields) - get_limit_choices_to = ( - fields and hasattr(fields[-1], 'rel') and - getattr(fields[-1].rel, 'get_limit_choices_to', None)) - if not get_limit_choices_to: - return models.Q() # empty Q - limit_choices_to = get_limit_choices_to() - if isinstance(limit_choices_to, models.Q): - return limit_choices_to # already a Q - else: - return models.Q(**limit_choices_to) # convert dict to Q diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 42a2f485ca1..0b38f7b0278 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -383,7 +383,7 @@ class ChangeList(object): except FieldDoesNotExist: pass else: - if isinstance(field.rel, models.ManyToOneRel): + if isinstance(field.remote_field, models.ManyToOneRel): return True return False diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 251cc0ef1c6..fd0021c0571 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -151,7 +151,7 @@ class ForeignKeyRawIdWidget(forms.TextInput): super(ForeignKeyRawIdWidget, self).__init__(attrs) def render(self, name, value, attrs=None): - rel_to = self.rel.to + rel_to = self.rel.model if attrs is None: attrs = {} extra = [] @@ -196,9 +196,9 @@ class ForeignKeyRawIdWidget(forms.TextInput): def label_for_value(self, value): key = self.rel.get_related_field().name try: - obj = self.rel.to._default_manager.using(self.db).get(**{key: value}) + obj = self.rel.model._default_manager.using(self.db).get(**{key: value}) return ' %s' % escape(Truncator(obj).words(14, truncate='...')) - except (ValueError, self.rel.to.DoesNotExist): + except (ValueError, self.rel.model.DoesNotExist): return '' @@ -210,7 +210,7 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): def render(self, name, value, attrs=None): if attrs is None: attrs = {} - if self.rel.to in self.admin_site._registry: + if self.rel.model in self.admin_site._registry: # The related object is registered with the same AdminSite attrs['class'] = 'vManyToManyRawIdAdminField' if value: @@ -248,7 +248,7 @@ class RelatedFieldWidgetWrapper(forms.Widget): # Backwards compatible check for whether a user can add related # objects. if can_add_related is None: - can_add_related = rel.to in admin_site._registry + can_add_related = rel.model in admin_site._registry self.can_add_related = can_add_related # XXX: The UX does not support multiple selected values. multiple = getattr(widget, 'allow_multiple_selected', False) @@ -280,7 +280,7 @@ class RelatedFieldWidgetWrapper(forms.Widget): def render(self, name, value, *args, **kwargs): from django.contrib.admin.views.main import IS_POPUP_VAR, TO_FIELD_VAR - rel_opts = self.rel.to._meta + rel_opts = self.rel.model._meta info = (rel_opts.app_label, rel_opts.model_name) self.widget.choices = self.choices url_params = '&'.join("%s=%s" % param for param in [ diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index 91ca143508a..245d0cf39e9 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -207,8 +207,8 @@ class ModelDetailView(BaseAdminDocsView): # ForeignKey is a special case since the field will actually be a # descriptor that returns the other object if isinstance(field, models.ForeignKey): - data_type = field.rel.to.__name__ - app_label = field.rel.to._meta.app_label + data_type = field.remote_field.model.__name__ + app_label = field.remote_field.model._meta.app_label verbose = utils.parse_rst( (_("the related `%(app_label)s.%(data_type)s` object") % { 'app_label': app_label, 'data_type': data_type, @@ -228,8 +228,8 @@ class ModelDetailView(BaseAdminDocsView): # Gather many-to-many fields. for field in opts.many_to_many: - data_type = field.rel.to.__name__ - app_label = field.rel.to._meta.app_label + data_type = field.remote_field.model.__name__ + app_label = field.remote_field.model._meta.app_label verbose = _("related `%(app_label)s.%(object_name)s` objects") % { 'app_label': app_label, 'object_name': data_type, diff --git a/django/contrib/auth/admin.py b/django/contrib/auth/admin.py index eb865a1c933..33106709950 100644 --- a/django/contrib/auth/admin.py +++ b/django/contrib/auth/admin.py @@ -31,7 +31,7 @@ class GroupAdmin(admin.ModelAdmin): def formfield_for_manytomany(self, db_field, request=None, **kwargs): if db_field.name == 'permissions': - qs = kwargs.get('queryset', db_field.rel.to.objects) + qs = kwargs.get('queryset', db_field.remote_field.model.objects) # Avoid a major performance hit resolving permission names which # triggers a content_type load: kwargs['queryset'] = qs.select_related('content_type') diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py index 65ad94b6949..c1251b32d62 100644 --- a/django/contrib/auth/management/commands/createsuperuser.py +++ b/django/contrib/auth/management/commands/createsuperuser.py @@ -90,11 +90,11 @@ class Command(BaseCommand): input_msg = capfirst(verbose_field_name) if default_username: input_msg += " (leave blank to use '%s')" % default_username - username_rel = self.username_field.rel + username_rel = self.username_field.remote_field input_msg = force_str('%s%s: ' % ( input_msg, ' (%s.%s)' % ( - username_rel.to._meta.object_name, + username_rel.model._meta.object_name, username_rel.field_name ) if username_rel else '') ) @@ -114,8 +114,13 @@ class Command(BaseCommand): field = self.UserModel._meta.get_field(field_name) user_data[field_name] = options.get(field_name) while user_data[field_name] is None: - message = force_str('%s%s: ' % (capfirst(field.verbose_name), - ' (%s.%s)' % (field.rel.to._meta.object_name, field.rel.field_name) if field.rel else '')) + message = force_str('%s%s: ' % ( + capfirst(field.verbose_name), + ' (%s.%s)' % ( + field.remote_field.model._meta.object_name, + field.remote_field.field_name, + ) if field.remote_field else '', + )) user_data[field_name] = self.get_input_data(field, message) # Get a password diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py index f72cd49b263..f5c1804a35a 100644 --- a/django/contrib/contenttypes/fields.py +++ b/django/contrib/contenttypes/fields.py @@ -39,6 +39,7 @@ class GenericForeignKey(object): one_to_many = False one_to_one = False related_model = None + remote_field = None allow_unsaved_instance_assignment = False @@ -135,7 +136,7 @@ class GenericForeignKey(object): id='contenttypes.E003', ) ] - elif field.rel.to != ContentType: + elif field.remote_field.model != ContentType: return [ checks.Error( "'%s.%s' is not a ForeignKey to 'contenttypes.ContentType'." % ( @@ -323,7 +324,7 @@ class GenericRelation(ForeignObject): return errors def _check_generic_foreign_key_existence(self): - target = self.rel.to + target = self.remote_field.model if isinstance(target, ModelBase): fields = target._meta.virtual_fields if any(isinstance(field, GenericForeignKey) and @@ -348,16 +349,16 @@ class GenericRelation(ForeignObject): def resolve_related_fields(self): self.to_fields = [self.model._meta.pk.name] - return [(self.rel.to._meta.get_field(self.object_id_field_name), self.model._meta.pk)] + return [(self.remote_field.model._meta.get_field(self.object_id_field_name), self.model._meta.pk)] def get_path_info(self): - opts = self.rel.to._meta + opts = self.remote_field.model._meta target = opts.pk - return [PathInfo(self.model._meta, opts, (target,), self.rel, True, False)] + return [PathInfo(self.model._meta, opts, (target,), self.remote_field, True, False)] def get_reverse_path_info(self): opts = self.model._meta - from_opts = self.rel.to._meta + from_opts = self.remote_field.model._meta return [PathInfo(from_opts, opts, (opts.pk,), self, not self.unique, False)] def get_choices_default(self): @@ -371,7 +372,7 @@ class GenericRelation(ForeignObject): kwargs['virtual_only'] = True super(GenericRelation, self).contribute_to_class(cls, name, **kwargs) self.model = cls - setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self.rel)) + setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self.remote_field)) def set_attributes_from_rel(self): pass @@ -387,7 +388,7 @@ class GenericRelation(ForeignObject): for_concrete_model=self.for_concrete_model) def get_extra_restriction(self, where_class, alias, remote_alias): - field = self.rel.to._meta.get_field(self.content_type_field_name) + field = self.remote_field.model._meta.get_field(self.content_type_field_name) contenttype_pk = self.get_content_type().pk cond = where_class() lookup = field.get_lookup('exact')(field.get_col(remote_alias), contenttype_pk) @@ -398,7 +399,7 @@ class GenericRelation(ForeignObject): """ Return all objects related to ``objs`` via this ``GenericRelation``. """ - return self.rel.to._base_manager.db_manager(using).filter(**{ + return self.remote_field.model._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] @@ -421,7 +422,7 @@ class ReverseGenericRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor): @cached_property def related_manager_cls(self): return create_generic_related_manager( - self.rel.to._default_manager.__class__, + self.rel.model._default_manager.__class__, self.rel, ) @@ -439,7 +440,7 @@ def create_generic_related_manager(superclass, rel): self.instance = instance - self.model = rel.to + self.model = rel.model content_type = ContentType.objects.db_manager(instance._state.db).get_for_model( instance, for_concrete_model=rel.field.for_concrete_model) diff --git a/django/contrib/contenttypes/forms.py b/django/contrib/contenttypes/forms.py index e590c3db05f..b862735adec 100644 --- a/django/contrib/contenttypes/forms.py +++ b/django/contrib/contenttypes/forms.py @@ -68,7 +68,7 @@ def generic_inlineformset_factory(model, form=ModelForm, 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: + if not isinstance(ct_field, models.ForeignKey) or ct_field.remote_field.model != 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: diff --git a/django/contrib/contenttypes/views.py b/django/contrib/contenttypes/views.py index 02446d023a2..e99459e4f2a 100644 --- a/django/contrib/contenttypes/views.py +++ b/django/contrib/contenttypes/views.py @@ -48,7 +48,7 @@ def shortcut(request, content_type_id, object_id): # First, look for an many-to-many relationship to Site. for field in opts.many_to_many: - if field.rel.to is Site: + if field.remote_field.model is Site: try: # Caveat: In the case of multiple related Sites, this just # selects the *first* one, which is arbitrary. @@ -61,7 +61,7 @@ def shortcut(request, content_type_id, object_id): # Next, look for a many-to-one relationship to Site. if object_domain is None: for field in obj._meta.fields: - if field.rel and field.rel.to is Site: + if field.remote_field and field.remote_field.model is Site: try: object_domain = getattr(obj, field.name).domain except Site.DoesNotExist: diff --git a/django/contrib/gis/db/models/lookups.py b/django/contrib/gis/db/models/lookups.py index 30b5c0435de..46104a2fa3a 100644 --- a/django/contrib/gis/db/models/lookups.py +++ b/django/contrib/gis/db/models/lookups.py @@ -45,7 +45,7 @@ class GISLookup(Lookup): # model field associated with the next field in the list # until there's no more left. while len(field_list): - opts = geo_fld.rel.to._meta + opts = geo_fld.remote_field.model._meta geo_fld = opts.get_field(field_list.pop()) except (FieldDoesNotExist, AttributeError): return False diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py index c9badc75220..1f7de823b75 100644 --- a/django/contrib/gis/utils/layermapping.py +++ b/django/contrib/gis/utils/layermapping.py @@ -229,7 +229,7 @@ class LayerMapping(object): elif isinstance(model_field, models.ForeignKey): if isinstance(ogr_name, dict): # Is every given related model mapping field in the Layer? - rel_model = model_field.rel.to + rel_model = model_field.remote_field.model for rel_name, ogr_field in ogr_name.items(): idx = check_ogr_fld(ogr_field) try: diff --git a/django/contrib/postgres/fields/array.py b/django/contrib/postgres/fields/array.py index 970355fd622..9da7ec4cb75 100644 --- a/django/contrib/postgres/fields/array.py +++ b/django/contrib/postgres/fields/array.py @@ -33,7 +33,7 @@ class ArrayField(Field): def check(self, **kwargs): errors = super(ArrayField, self).check(**kwargs) - if self.base_field.rel: + if self.base_field.remote_field: errors.append( checks.Error( 'Base field for array cannot be a related field.', diff --git a/django/core/serializers/__init__.py b/django/core/serializers/__init__.py index 5d049a4f918..19216bdfa70 100644 --- a/django/core/serializers/__init__.py +++ b/django/core/serializers/__init__.py @@ -184,16 +184,16 @@ def sort_dependencies(app_list): # Now add a dependency for any FK relation with a model that # defines a natural key for field in model._meta.fields: - if hasattr(field.rel, 'to'): - rel_model = field.rel.to + if field.remote_field: + rel_model = field.remote_field.model if hasattr(rel_model, 'natural_key') and rel_model != model: deps.append(rel_model) # Also add a dependency for any simple M2M relation with a model # that defines a natural key. M2M relations with explicit through # models don't count as dependencies. for field in model._meta.many_to_many: - if field.rel.through._meta.auto_created: - rel_model = field.rel.to + if field.remote_field.through._meta.auto_created: + rel_model = field.remote_field.model if hasattr(rel_model, 'natural_key') and rel_model != model: deps.append(rel_model) model_dependencies.append((model, deps)) diff --git a/django/core/serializers/base.py b/django/core/serializers/base.py index 7f00e7147d0..2c18d7b477a 100644 --- a/django/core/serializers/base.py +++ b/django/core/serializers/base.py @@ -49,7 +49,7 @@ class Serializer(object): concrete_model = obj._meta.concrete_model for field in concrete_model._meta.local_fields: if field.serialize: - if field.rel is None: + if field.remote_field is None: if self.selected_fields is None or field.attname in self.selected_fields: self.handle_field(obj, field) else: diff --git a/django/core/serializers/python.py b/django/core/serializers/python.py index 0b18f918584..7caff520669 100644 --- a/django/core/serializers/python.py +++ b/django/core/serializers/python.py @@ -55,7 +55,7 @@ class Serializer(base.Serializer): self._current[field.name] = field.value_to_string(obj) def handle_fk_field(self, obj, field): - if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'): + if self.use_natural_foreign_keys and hasattr(field.remote_field.model, 'natural_key'): related = getattr(obj, field.name) if related: value = related.natural_key() @@ -68,8 +68,8 @@ class Serializer(base.Serializer): self._current[field.name] = value def handle_m2m_field(self, obj, field): - if field.rel.through._meta.auto_created: - if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'): + if field.remote_field.through._meta.auto_created: + if self.use_natural_foreign_keys and hasattr(field.remote_field.model, 'natural_key'): m2m_value = lambda value: value.natural_key() else: m2m_value = lambda value: force_text(value._get_pk_val(), strings_only=True) @@ -120,33 +120,33 @@ def Deserializer(object_list, **options): field = Model._meta.get_field(field_name) # Handle M2M relations - if field.rel and isinstance(field.rel, models.ManyToManyRel): - if hasattr(field.rel.to._default_manager, 'get_by_natural_key'): + if field.remote_field and isinstance(field.remote_field, models.ManyToManyRel): + if hasattr(field.remote_field.model._default_manager, 'get_by_natural_key'): def m2m_convert(value): if hasattr(value, '__iter__') and not isinstance(value, six.text_type): - return field.rel.to._default_manager.db_manager(db).get_by_natural_key(*value).pk + return field.remote_field.model._default_manager.db_manager(db).get_by_natural_key(*value).pk else: - return force_text(field.rel.to._meta.pk.to_python(value), strings_only=True) + return force_text(field.remote_field.model._meta.pk.to_python(value), strings_only=True) else: - m2m_convert = lambda v: force_text(field.rel.to._meta.pk.to_python(v), strings_only=True) + m2m_convert = lambda v: force_text(field.remote_field.model._meta.pk.to_python(v), strings_only=True) m2m_data[field.name] = [m2m_convert(pk) for pk in field_value] # Handle FK fields - elif field.rel and isinstance(field.rel, models.ManyToOneRel): + elif field.remote_field and isinstance(field.remote_field, models.ManyToOneRel): if field_value is not None: - if hasattr(field.rel.to._default_manager, 'get_by_natural_key'): + if hasattr(field.remote_field.model._default_manager, 'get_by_natural_key'): if hasattr(field_value, '__iter__') and not isinstance(field_value, six.text_type): - obj = field.rel.to._default_manager.db_manager(db).get_by_natural_key(*field_value) - value = getattr(obj, field.rel.field_name) + obj = field.remote_field.model._default_manager.db_manager(db).get_by_natural_key(*field_value) + value = getattr(obj, field.remote_field.field_name) # If this is a natural foreign key to an object that # has a FK/O2O as the foreign key, use the FK value - if field.rel.to._meta.pk.rel: + if field.remote_field.model._meta.pk.remote_field: value = value.pk else: - value = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value) + value = field.remote_field.model._meta.get_field(field.remote_field.field_name).to_python(field_value) data[field.attname] = value else: - data[field.attname] = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value) + data[field.attname] = field.remote_field.model._meta.get_field(field.remote_field.field_name).to_python(field_value) else: data[field.attname] = None diff --git a/django/core/serializers/xml_serializer.py b/django/core/serializers/xml_serializer.py index 73bed113b90..e22be93f01d 100644 --- a/django/core/serializers/xml_serializer.py +++ b/django/core/serializers/xml_serializer.py @@ -91,7 +91,7 @@ class Serializer(base.Serializer): self._start_relational_field(field) related_att = getattr(obj, field.get_attname()) if related_att is not None: - if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'): + if self.use_natural_foreign_keys and hasattr(field.remote_field.model, 'natural_key'): related = getattr(obj, field.name) # If related object has a natural key, use it related = related.natural_key() @@ -112,9 +112,9 @@ class Serializer(base.Serializer): serialized as references to the object's PK (i.e. the related *data* is not dumped, just the relation). """ - if field.rel.through._meta.auto_created: + if field.remote_field.through._meta.auto_created: self._start_relational_field(field) - if self.use_natural_foreign_keys and hasattr(field.rel.to, 'natural_key'): + if self.use_natural_foreign_keys and hasattr(field.remote_field.model, 'natural_key'): # If the objects in the m2m have a natural key, use it def handle_m2m(value): natural = value.natural_key() @@ -142,8 +142,8 @@ class Serializer(base.Serializer): self.indent(2) self.xml.startElement("field", { "name": field.name, - "rel": field.rel.__class__.__name__, - "to": smart_text(field.rel.to._meta), + "rel": field.remote_field.__class__.__name__, + "to": smart_text(field.remote_field.model._meta), }) @@ -204,9 +204,9 @@ class Deserializer(base.Deserializer): field = Model._meta.get_field(field_name) # As is usually the case, relation fields get the special treatment. - if field.rel and isinstance(field.rel, models.ManyToManyRel): + if field.remote_field and isinstance(field.remote_field, models.ManyToManyRel): m2m_data[field.name] = self._handle_m2m_field_node(field_node, field) - elif field.rel and isinstance(field.rel, models.ManyToOneRel): + elif field.remote_field and isinstance(field.remote_field, models.ManyToOneRel): data[field.attname] = self._handle_fk_field_node(field_node, field) else: if field_node.getElementsByTagName('None'): @@ -228,43 +228,43 @@ class Deserializer(base.Deserializer): if node.getElementsByTagName('None'): return None else: - if hasattr(field.rel.to._default_manager, 'get_by_natural_key'): + if hasattr(field.remote_field.model._default_manager, 'get_by_natural_key'): keys = node.getElementsByTagName('natural') if keys: # If there are 'natural' subelements, it must be a natural key field_value = [getInnerText(k).strip() for k in keys] - obj = field.rel.to._default_manager.db_manager(self.db).get_by_natural_key(*field_value) - obj_pk = getattr(obj, field.rel.field_name) + obj = field.remote_field.model._default_manager.db_manager(self.db).get_by_natural_key(*field_value) + obj_pk = getattr(obj, field.remote_field.field_name) # If this is a natural foreign key to an object that # has a FK/O2O as the foreign key, use the FK value - if field.rel.to._meta.pk.rel: + if field.remote_field.model._meta.pk.remote_field: obj_pk = obj_pk.pk else: # Otherwise, treat like a normal PK field_value = getInnerText(node).strip() - obj_pk = field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value) + obj_pk = field.remote_field.model._meta.get_field(field.remote_field.field_name).to_python(field_value) return obj_pk else: field_value = getInnerText(node).strip() - return field.rel.to._meta.get_field(field.rel.field_name).to_python(field_value) + return field.remote_field.model._meta.get_field(field.remote_field.field_name).to_python(field_value) def _handle_m2m_field_node(self, node, field): """ Handle a node for a ManyToManyField. """ - if hasattr(field.rel.to._default_manager, 'get_by_natural_key'): + if hasattr(field.remote_field.model._default_manager, 'get_by_natural_key'): def m2m_convert(n): keys = n.getElementsByTagName('natural') if keys: # If there are 'natural' subelements, it must be a natural key field_value = [getInnerText(k).strip() for k in keys] - obj_pk = field.rel.to._default_manager.db_manager(self.db).get_by_natural_key(*field_value).pk + obj_pk = field.remote_field.model._default_manager.db_manager(self.db).get_by_natural_key(*field_value).pk else: # Otherwise, treat like a normal PK value. - obj_pk = field.rel.to._meta.pk.to_python(n.getAttribute('pk')) + obj_pk = field.remote_field.model._meta.pk.to_python(n.getAttribute('pk')) return obj_pk else: - m2m_convert = lambda n: field.rel.to._meta.pk.to_python(n.getAttribute('pk')) + m2m_convert = lambda n: field.remote_field.model._meta.pk.to_python(n.getAttribute('pk')) return [m2m_convert(c) for c in node.getElementsByTagName("object")] def _get_model_from_node(self, node, attr): diff --git a/django/db/backends/base/introspection.py b/django/db/backends/base/introspection.py index 5f828b94ccf..762cca441ff 100644 --- a/django/db/backends/base/introspection.py +++ b/django/db/backends/base/introspection.py @@ -125,7 +125,7 @@ class BaseDatabaseIntrospection(object): for f in model._meta.local_many_to_many: # If this is an m2m using an intermediate table, # we don't need to reset the sequence. - if f.rel.through is None: + if f.remote_field.through is None: sequence_list.append({'table': f.m2m_db_table(), 'column': None}) return sequence_list diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 167c95dbd04..5c3bf8baee1 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -241,9 +241,9 @@ class BaseDatabaseSchemaEditor(object): definition += " %s" % col_type_suffix params.extend(extra_params) # FK - if field.rel and field.db_constraint: - to_table = field.rel.to._meta.db_table - to_column = field.rel.to._meta.get_field(field.rel.field_name).column + if field.remote_field and field.db_constraint: + to_table = field.remote_field.model._meta.db_table + to_column = field.remote_field.model._meta.get_field(field.remote_field.field_name).column if self.connection.features.supports_foreign_keys: self.deferred_sql.append(self._create_fk_sql(model, field, "_fk_%(to_table)s_%(to_column)s")) elif self.sql_create_inline_fk: @@ -284,8 +284,8 @@ class BaseDatabaseSchemaEditor(object): # Make M2M tables for field in model._meta.local_many_to_many: - if field.rel.through._meta.auto_created: - self.create_model(field.rel.through) + if field.remote_field.through._meta.auto_created: + self.create_model(field.remote_field.through) def delete_model(self, model): """ @@ -293,8 +293,8 @@ class BaseDatabaseSchemaEditor(object): """ # Handle auto-created intermediary models for field in model._meta.local_many_to_many: - if field.rel.through._meta.auto_created: - self.delete_model(field.rel.through) + if field.remote_field.through._meta.auto_created: + self.delete_model(field.remote_field.through) # Delete the table self.execute(self.sql_delete_table % { @@ -377,8 +377,8 @@ class BaseDatabaseSchemaEditor(object): table instead (for M2M fields) """ # Special-case implicit M2M tables - if field.many_to_many and field.rel.through._meta.auto_created: - return self.create_model(field.rel.through) + if field.many_to_many and field.remote_field.through._meta.auto_created: + return self.create_model(field.remote_field.through) # Get the column's definition definition, params = self.column_sql(model, field, include_default=True) # It might not actually have a column behind it @@ -409,7 +409,7 @@ class BaseDatabaseSchemaEditor(object): if field.db_index and not field.unique: self.deferred_sql.append(self._create_index_sql(model, [field])) # Add any FK constraints later - if field.rel and self.connection.features.supports_foreign_keys and field.db_constraint: + if field.remote_field and self.connection.features.supports_foreign_keys and field.db_constraint: self.deferred_sql.append(self._create_fk_sql(model, field, "_fk_%(to_table)s_%(to_column)s")) # Reset connection if required if self.connection.features.connection_persists_old_columns: @@ -421,13 +421,13 @@ class BaseDatabaseSchemaEditor(object): but for M2Ms may involve deleting a table. """ # Special-case implicit M2M tables - if field.many_to_many and field.rel.through._meta.auto_created: - return self.delete_model(field.rel.through) + if field.many_to_many and field.remote_field.through._meta.auto_created: + return self.delete_model(field.remote_field.through) # It might not actually have a column behind it if field.db_parameters(connection=self.connection)['type'] is None: return # Drop any FK constraints, MySQL requires explicit deletion - if field.rel: + if field.remote_field: fk_names = self._constraint_names(model, [field.column], foreign_key=True) for fk_name in fk_names: self.execute(self._delete_constraint_sql(self.sql_delete_fk, model, fk_name)) @@ -454,21 +454,21 @@ class BaseDatabaseSchemaEditor(object): old_type = old_db_params['type'] new_db_params = new_field.db_parameters(connection=self.connection) new_type = new_db_params['type'] - if (old_type is None and old_field.rel is None) or (new_type is None and new_field.rel is None): + if (old_type is None and old_field.remote_field is None) or (new_type is None and new_field.remote_field is None): raise ValueError( "Cannot alter field %s into %s - they do not properly define " "db_type (are you using PostGIS 1.5 or badly-written custom " "fields?)" % (old_field, new_field), ) elif old_type is None and new_type is None and ( - old_field.rel.through and new_field.rel.through and - old_field.rel.through._meta.auto_created and - new_field.rel.through._meta.auto_created): + old_field.remote_field.through and new_field.remote_field.through and + old_field.remote_field.through._meta.auto_created and + new_field.remote_field.through._meta.auto_created): return self._alter_many_to_many(model, old_field, new_field, strict) elif old_type is None and new_type is None and ( - old_field.rel.through and new_field.rel.through and - not old_field.rel.through._meta.auto_created and - not new_field.rel.through._meta.auto_created): + old_field.remote_field.through and new_field.remote_field.through and + not old_field.remote_field.through._meta.auto_created and + not new_field.remote_field.through._meta.auto_created): # Both sides have through models; this is a no-op. return elif old_type is None or new_type is None: @@ -487,7 +487,7 @@ class BaseDatabaseSchemaEditor(object): # Drop any FK constraints, we'll remake them later fks_dropped = set() - if old_field.rel and old_field.db_constraint: + if old_field.remote_field and old_field.db_constraint: fk_names = self._constraint_names(model, [old_field.column], foreign_key=True) if strict and len(fk_names) != 1: raise ValueError("Found wrong number (%s) of foreign key constraints for %s.%s" % ( @@ -707,8 +707,8 @@ class BaseDatabaseSchemaEditor(object): } ) # Does it have a foreign key? - if (new_field.rel and - (fks_dropped or not old_field.rel or not old_field.db_constraint) and + if (new_field.remote_field and + (fks_dropped or not old_field.remote_field or not old_field.db_constraint) and new_field.db_constraint): self.execute(self._create_fk_sql(model, new_field, "_fk_%(to_table)s_%(to_column)s")) # Rebuild FKs that pointed to us if we previously had to drop them @@ -766,22 +766,22 @@ class BaseDatabaseSchemaEditor(object): Alters M2Ms to repoint their to= endpoints. """ # Rename the through table - if old_field.rel.through._meta.db_table != new_field.rel.through._meta.db_table: - self.alter_db_table(old_field.rel.through, old_field.rel.through._meta.db_table, - new_field.rel.through._meta.db_table) + if old_field.remote_field.through._meta.db_table != new_field.remote_field.through._meta.db_table: + self.alter_db_table(old_field.remote_field.through, old_field.remote_field.through._meta.db_table, + new_field.remote_field.through._meta.db_table) # Repoint the FK to the other side self.alter_field( - new_field.rel.through, + new_field.remote_field.through, # We need the field that points to the target model, so we can tell alter_field to change it - # this is m2m_reverse_field_name() (as opposed to m2m_field_name, which points to our model) - old_field.rel.through._meta.get_field(old_field.m2m_reverse_field_name()), - new_field.rel.through._meta.get_field(new_field.m2m_reverse_field_name()), + old_field.remote_field.through._meta.get_field(old_field.m2m_reverse_field_name()), + new_field.remote_field.through._meta.get_field(new_field.m2m_reverse_field_name()), ) self.alter_field( - new_field.rel.through, + new_field.remote_field.through, # for self-referential models we need to alter field from the other end too - old_field.rel.through._meta.get_field(old_field.m2m_field_name()), - new_field.rel.through._meta.get_field(new_field.m2m_field_name()), + old_field.remote_field.through._meta.get_field(old_field.m2m_field_name()), + new_field.remote_field.through._meta.get_field(new_field.m2m_field_name()), ) def _create_index_name(self, model, column_names, suffix=""): @@ -860,8 +860,8 @@ class BaseDatabaseSchemaEditor(object): def _create_fk_sql(self, model, field, suffix): from_table = model._meta.db_table from_column = field.column - to_table = field.related_field.model._meta.db_table - to_column = field.related_field.column + to_table = field.target_field.model._meta.db_table + to_column = field.target_field.column suffix = suffix % { "to_table": to_table, "to_column": to_column, diff --git a/django/db/backends/mysql/validation.py b/django/db/backends/mysql/validation.py index 9dcd14403ff..94352db6170 100644 --- a/django/db/backends/mysql/validation.py +++ b/django/db/backends/mysql/validation.py @@ -14,7 +14,7 @@ class DatabaseValidation(BaseDatabaseValidation): errors = super(DatabaseValidation, self).check_field(field, **kwargs) # Ignore any related fields. - if getattr(field, 'rel', None) is None: + if getattr(field, 'remote_field', None) is None: field_type = field.db_type(connection) # Ignore any non-concrete fields diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index 02fd6fcc7d5..32dd6272169 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -346,7 +346,7 @@ WHEN (new.%(col_name)s IS NULL) # continue to loop break for f in model._meta.many_to_many: - if not f.rel.through: + if not f.remote_field.through: table_name = self.quote_name(f.m2m_db_table()) sequence_name = self._get_sequence_name(f.m2m_db_table()) column_name = self.quote_name('id') diff --git a/django/db/backends/postgresql_psycopg2/operations.py b/django/db/backends/postgresql_psycopg2/operations.py index 7483d97aee7..df96d16ad23 100644 --- a/django/db/backends/postgresql_psycopg2/operations.py +++ b/django/db/backends/postgresql_psycopg2/operations.py @@ -172,7 +172,7 @@ class DatabaseOperations(BaseDatabaseOperations): ) break # Only one AutoField is allowed per model, so don't bother continuing. for f in model._meta.many_to_many: - if not f.rel.through: + if not f.remote_field.through: output.append( "%s setval(pg_get_serial_sequence('%s','%s'), " "coalesce(max(%s), 1), max(%s) %s null) %s %s;" % ( diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py index 26c4771f8fd..0664d31e7a0 100644 --- a/django/db/backends/sqlite3/schema.py +++ b/django/db/backends/sqlite3/schema.py @@ -118,8 +118,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): del body[field.name] del mapping[field.column] # Remove any implicit M2M tables - if field.many_to_many and field.rel.through._meta.auto_created: - return self.delete_model(field.rel.through) + if field.many_to_many and field.remote_field.through._meta.auto_created: + return self.delete_model(field.remote_field.through) # Work inside a new app registry apps = Apps() @@ -215,8 +215,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): table instead (for M2M fields) """ # Special-case implicit M2M tables - if field.many_to_many and field.rel.through._meta.auto_created: - return self.create_model(field.rel.through) + if field.many_to_many and field.remote_field.through._meta.auto_created: + return self.create_model(field.remote_field.through) self._remake_table(model, create_fields=[field]) def remove_field(self, model, field): @@ -227,8 +227,8 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): # M2M fields are a special case if field.many_to_many: # For implicit M2M tables, delete the auto-created table - if field.rel.through._meta.auto_created: - self.delete_model(field.rel.through) + if field.remote_field.through._meta.auto_created: + self.delete_model(field.remote_field.through) # For explicit "through" M2M fields, do nothing # For everything else, remake. else: @@ -263,25 +263,25 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): """ Alters M2Ms to repoint their to= endpoints. """ - if old_field.rel.through._meta.db_table == new_field.rel.through._meta.db_table: + if old_field.remote_field.through._meta.db_table == new_field.remote_field.through._meta.db_table: # The field name didn't change, but some options did; we have to propagate this altering. self._remake_table( - old_field.rel.through, + old_field.remote_field.through, alter_fields=[( # We need the field that points to the target model, so we can tell alter_field to change it - # this is m2m_reverse_field_name() (as opposed to m2m_field_name, which points to our model) - old_field.rel.through._meta.get_field(old_field.m2m_reverse_field_name()), - new_field.rel.through._meta.get_field(new_field.m2m_reverse_field_name()), + old_field.remote_field.through._meta.get_field(old_field.m2m_reverse_field_name()), + new_field.remote_field.through._meta.get_field(new_field.m2m_reverse_field_name()), )], override_uniques=(new_field.m2m_field_name(), new_field.m2m_reverse_field_name()), ) return # Make a new through table - self.create_model(new_field.rel.through) + self.create_model(new_field.remote_field.through) # Copy the data across self.execute("INSERT INTO %s (%s) SELECT %s FROM %s" % ( - self.quote_name(new_field.rel.through._meta.db_table), + self.quote_name(new_field.remote_field.through._meta.db_table), ', '.join([ "id", new_field.m2m_column_name(), @@ -292,7 +292,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor): old_field.m2m_column_name(), old_field.m2m_reverse_name(), ]), - self.quote_name(old_field.rel.through._meta.db_table), + self.quote_name(old_field.remote_field.through._meta.db_table), )) # Delete the old through table - self.delete_model(old_field.rel.through) + self.delete_model(old_field.remote_field.through) diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index 33a9e9e1663..786de794df5 100644 --- a/django/db/migrations/autodetector.py +++ b/django/db/migrations/autodetector.py @@ -78,7 +78,7 @@ class MigrationAutodetector(object): fields_def = [] for name, field in fields: deconstruction = self.deep_deconstruct(field) - if field.rel and field.rel.to: + if field.remote_field and field.remote_field.model: del deconstruction[2]['to'] fields_def.append(deconstruction) return fields_def @@ -163,11 +163,11 @@ class MigrationAutodetector(object): old_model_state = self.from_state.models[app_label, old_model_name] for field_name, field in old_model_state.fields: old_field = self.old_apps.get_model(app_label, old_model_name)._meta.get_field(field_name) - if (hasattr(old_field, "rel") and getattr(old_field.rel, "through", None) - and not old_field.rel.through._meta.auto_created): + if (hasattr(old_field, "remote_field") and getattr(old_field.remote_field, "through", None) + and not old_field.remote_field.through._meta.auto_created): through_key = ( - old_field.rel.through._meta.app_label, - old_field.rel.through._meta.model_name, + old_field.remote_field.through._meta.app_label, + old_field.remote_field.through._meta.model_name, ) self.through_users[through_key] = (app_label, old_model_name, field_name) @@ -460,20 +460,20 @@ class MigrationAutodetector(object): related_fields = {} primary_key_rel = None for field in model_opts.local_fields: - if field.rel: - if field.rel.to: + if field.remote_field: + if field.remote_field.model: if field.primary_key: - primary_key_rel = field.rel.to - elif not field.rel.parent_link: + primary_key_rel = field.remote_field.model + elif not field.remote_field.parent_link: related_fields[field.name] = field # through will be none on M2Ms on swapped-out models; # we can treat lack of through as auto_created=True, though. - if getattr(field.rel, "through", None) and not field.rel.through._meta.auto_created: + if getattr(field.remote_field, "through", None) and not field.remote_field.through._meta.auto_created: related_fields[field.name] = field for field in model_opts.local_many_to_many: - if field.rel.to: + if field.remote_field.model: related_fields[field.name] = field - if getattr(field.rel, "through", None) and not field.rel.through._meta.auto_created: + if getattr(field.remote_field, "through", None) and not field.remote_field.through._meta.auto_created: related_fields[field.name] = field # Are there unique/index_together to defer? unique_together = model_state.options.pop('unique_together', None) @@ -522,13 +522,13 @@ class MigrationAutodetector(object): dep_app_label = "__setting__" dep_object_name = swappable_setting else: - dep_app_label = field.rel.to._meta.app_label - dep_object_name = field.rel.to._meta.object_name + dep_app_label = field.remote_field.model._meta.app_label + dep_object_name = field.remote_field.model._meta.object_name dependencies = [(dep_app_label, dep_object_name, None, True)] - if getattr(field.rel, "through", None) and not field.rel.through._meta.auto_created: + if getattr(field.remote_field, "through", None) and not field.remote_field.through._meta.auto_created: dependencies.append(( - field.rel.through._meta.app_label, - field.rel.through._meta.object_name, + field.remote_field.through._meta.app_label, + field.remote_field.through._meta.object_name, None, True )) @@ -639,17 +639,17 @@ class MigrationAutodetector(object): # Gather related fields related_fields = {} for field in model._meta.local_fields: - if field.rel: - if field.rel.to: + if field.remote_field: + if field.remote_field.model: related_fields[field.name] = field # through will be none on M2Ms on swapped-out models; # we can treat lack of through as auto_created=True, though. - if getattr(field.rel, "through", None) and not field.rel.through._meta.auto_created: + if getattr(field.remote_field, "through", None) and not field.remote_field.through._meta.auto_created: related_fields[field.name] = field for field in model._meta.local_many_to_many: - if field.rel.to: + if field.remote_field.model: related_fields[field.name] = field - if getattr(field.rel, "through", None) and not field.rel.through._meta.auto_created: + if getattr(field.remote_field, "through", None) and not field.remote_field.through._meta.auto_created: related_fields[field.name] = field # Generate option removal first unique_together = model_state.options.pop('unique_together', None) @@ -736,7 +736,7 @@ class MigrationAutodetector(object): for rem_app_label, rem_model_name, rem_field_name in sorted(self.old_field_keys - self.new_field_keys): if rem_app_label == app_label and rem_model_name == model_name: old_field_dec = self.deep_deconstruct(old_model_state.get_field_by_name(rem_field_name)) - if field.rel and field.rel.to and 'to' in old_field_dec[2]: + if field.remote_field and field.remote_field.model and 'to' in old_field_dec[2]: old_rel_to = old_field_dec[2]['to'] if old_rel_to in self.renamed_models_rel: old_field_dec[2]['to'] = self.renamed_models_rel[old_rel_to] @@ -766,20 +766,20 @@ class MigrationAutodetector(object): field = self.new_apps.get_model(app_label, model_name)._meta.get_field(field_name) # Fields that are foreignkeys/m2ms depend on stuff dependencies = [] - if field.rel and field.rel.to: + if field.remote_field and field.remote_field.model: # Account for FKs to swappable models swappable_setting = getattr(field, 'swappable_setting', None) if swappable_setting is not None: dep_app_label = "__setting__" dep_object_name = swappable_setting else: - dep_app_label = field.rel.to._meta.app_label - dep_object_name = field.rel.to._meta.object_name + dep_app_label = field.remote_field.model._meta.app_label + dep_object_name = field.remote_field.model._meta.object_name dependencies = [(dep_app_label, dep_object_name, None, True)] - if getattr(field.rel, "through", None) and not field.rel.through._meta.auto_created: + if getattr(field.remote_field, "through", None) and not field.remote_field.through._meta.auto_created: dependencies.append(( - field.rel.through._meta.app_label, - field.rel.through._meta.object_name, + field.remote_field.through._meta.app_label, + field.remote_field.through._meta.object_name, None, True, )) @@ -838,13 +838,13 @@ class MigrationAutodetector(object): new_field = self.new_apps.get_model(app_label, model_name)._meta.get_field(field_name) # Implement any model renames on relations; these are handled by RenameModel # so we need to exclude them from the comparison - if hasattr(new_field, "rel") and getattr(new_field.rel, "to", None): + if hasattr(new_field, "remote_field") and getattr(new_field.remote_field, "model", None): rename_key = ( - new_field.rel.to._meta.app_label, - new_field.rel.to._meta.model_name, + new_field.remote_field.model._meta.app_label, + new_field.remote_field.model._meta.model_name, ) if rename_key in self.renamed_models: - new_field.rel.to = old_field.rel.to + new_field.remote_field.model = old_field.remote_field.model old_field_dec = self.deep_deconstruct(old_field) new_field_dec = self.deep_deconstruct(new_field) if old_field_dec != new_field_dec: diff --git a/django/db/migrations/operations/fields.py b/django/db/migrations/operations/fields.py index e00cfa66e22..ea4092ab26a 100644 --- a/django/db/migrations/operations/fields.py +++ b/django/db/migrations/operations/fields.py @@ -191,11 +191,11 @@ class AlterField(Operation): # If the field is a relatedfield with an unresolved rel.to, just # set it equal to the other field side. Bandaid fix for AlterField # migrations that are part of a RenameModel change. - if from_field.rel and from_field.rel.to: - if isinstance(from_field.rel.to, six.string_types): - from_field.rel.to = to_field.rel.to - elif to_field.rel and isinstance(to_field.rel.to, six.string_types): - to_field.rel.to = from_field.rel.to + if from_field.remote_field and from_field.remote_field.model: + if isinstance(from_field.remote_field.model, six.string_types): + from_field.remote_field.model = to_field.remote_field.model + elif to_field.remote_field and isinstance(to_field.remote_field.model, six.string_types): + to_field.remote_field.model = from_field.remote_field.model if not self.preserve_default: to_field.default = self.field.default schema_editor.alter_field(from_model, from_field, to_field) diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py index efd35f0501a..ece15274530 100644 --- a/django/db/migrations/operations/models.py +++ b/django/db/migrations/operations/models.py @@ -74,9 +74,9 @@ class CreateModel(Operation): strings_to_check.append(base.split(".")[-1]) # Check we have no FKs/M2Ms with it for fname, field in self.fields: - if field.rel: - if isinstance(field.rel.to, six.string_types): - strings_to_check.append(field.rel.to.split(".")[-1]) + if field.remote_field: + if isinstance(field.remote_field.model, six.string_types): + strings_to_check.append(field.remote_field.model.split(".")[-1]) # Now go over all the strings and compare them for string in strings_to_check: if string.lower() == name.lower(): @@ -181,7 +181,7 @@ class RenameModel(Operation): for name, field in state.models[related_key].fields: if name == related_object.field.name: field = field.clone() - field.rel.to = "%s.%s" % (app_label, self.new_name) + field.remote_field.model = "%s.%s" % (app_label, self.new_name) new_fields.append((name, field)) state.models[related_key].fields = new_fields state.reload_model(*related_key) @@ -220,11 +220,11 @@ class RenameModel(Operation): fields = zip(old_model._meta.local_many_to_many, new_model._meta.local_many_to_many) for (old_field, new_field) in fields: # Skip self-referential fields as these are renamed above. - if new_field.model == new_field.related_model or not new_field.rel.through._meta.auto_created: + if new_field.model == new_field.related_model or not new_field.remote_field.through._meta.auto_created: continue # Rename the M2M table that's based on this model's name. - old_m2m_model = old_field.rel.through - new_m2m_model = new_field.rel.through + old_m2m_model = old_field.remote_field.through + new_m2m_model = new_field.remote_field.through schema_editor.alter_db_table( new_m2m_model, old_m2m_model._meta.db_table, @@ -296,11 +296,11 @@ class AlterModelTable(Operation): ) # Rename M2M fields whose name is based on this model's db_table for (old_field, new_field) in zip(old_model._meta.local_many_to_many, new_model._meta.local_many_to_many): - if new_field.rel.through._meta.auto_created: + if new_field.remote_field.through._meta.auto_created: schema_editor.alter_db_table( - new_field.rel.through, - old_field.rel.through._meta.db_table, - new_field.rel.through._meta.db_table, + new_field.remote_field.through, + old_field.remote_field.through._meta.db_table, + new_field.remote_field.through._meta.db_table, ) def database_backwards(self, app_label, schema_editor, from_state, to_state): diff --git a/django/db/migrations/optimizer.py b/django/db/migrations/optimizer.py index 8c5ee718194..5bb97b5fe49 100644 --- a/django/db/migrations/optimizer.py +++ b/django/db/migrations/optimizer.py @@ -230,15 +230,15 @@ class MigrationOptimizer(object): def reduce_create_model_add_field(self, operation, other, in_between): if operation.name_lower == other.model_name_lower: # Don't allow optimizations of FKs through models they reference - if hasattr(other.field, "rel") and other.field.rel: + if hasattr(other.field, "remote_field") and other.field.remote_field: for between in in_between: # Check that it doesn't point to the model - app_label, object_name = self.model_to_key(other.field.rel.to) + app_label, object_name = self.model_to_key(other.field.remote_field.model) if between.references_model(object_name, app_label): return None # Check that it's not through the model - if getattr(other.field.rel, "through", None): - app_label, object_name = self.model_to_key(other.field.rel.through) + if getattr(other.field.remote_field, "through", None): + app_label, object_name = self.model_to_key(other.field.remote_field.through) if between.references_model(object_name, app_label): return None # OK, that's fine diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index 3d78cf609e3..d0d065af8ae 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -94,9 +94,9 @@ class ProjectState(object): model_state = self.models[(app_label, model_name)] for name, field in model_state.fields: if field.is_relation: - if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT: + if field.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT: continue - rel_app_label, rel_model_name = _get_app_label_and_model_name(field.rel.to, app_label) + rel_app_label, rel_model_name = _get_app_label_and_model_name(field.remote_field.model, app_label) related_models.add((rel_app_label, rel_model_name.lower())) # Unregister all related models @@ -328,7 +328,7 @@ class ModelState(object): # Deconstruct the fields fields = [] for field in model._meta.local_fields: - if getattr(field, "rel", None) and exclude_rels: + if getattr(field, "remote_field", None) and exclude_rels: continue if isinstance(field, OrderWrt): continue diff --git a/django/db/models/base.py b/django/db/models/base.py index 06a90882f09..fdd1ce28d9d 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -199,7 +199,7 @@ class ModelBase(type): # Locate OneToOneField instances. for field in base._meta.local_fields: if isinstance(field, OneToOneField): - parent_links[field.rel.to] = field + parent_links[field.remote_field.model] = field # Do the appropriate setup for any model parents. for base in parents: @@ -307,19 +307,19 @@ class ModelBase(type): # certain it has been created def make_foreign_order_accessors(field, model, cls): setattr( - field.rel.to, + field.remote_field.model, 'get_%s_order' % cls.__name__.lower(), curry(method_get_order, cls) ) setattr( - field.rel.to, + field.remote_field.model, 'set_%s_order' % cls.__name__.lower(), curry(method_set_order, cls) ) add_lazy_relation( cls, opts.order_with_respect_to, - opts.order_with_respect_to.rel.to, + opts.order_with_respect_to.remote_field.model, make_foreign_order_accessors ) @@ -382,7 +382,7 @@ class Model(six.with_metaclass(ModelBase)): setattr(self, field.attname, val) kwargs.pop(field.name, None) # Maintain compatibility with existing calls. - if isinstance(field.rel, ManyToOneRel): + if isinstance(field.remote_field, ManyToOneRel): kwargs.pop(field.attname, None) # Now we're left with the unprocessed fields that *must* come from @@ -399,7 +399,7 @@ class Model(six.with_metaclass(ModelBase)): # This field will be populated on request. continue if kwargs: - if isinstance(field.rel, ForeignObjectRel): + if isinstance(field.remote_field, ForeignObjectRel): try: # Assume object instance was passed in. rel_obj = kwargs.pop(field.name) @@ -879,7 +879,7 @@ class Model(six.with_metaclass(ModelBase)): def prepare_database_save(self, field): if self.pk is None: raise ValueError("Unsaved model instance %r cannot be used in an ORM query." % self) - return getattr(self, field.rel.field_name) + return getattr(self, field.remote_field.field_name) def clean(self): """ @@ -1238,20 +1238,20 @@ class Model(six.with_metaclass(ModelBase)): fields = cls._meta.local_many_to_many # Skip when the target model wasn't found. - fields = (f for f in fields if isinstance(f.rel.to, ModelBase)) + fields = (f for f in fields if isinstance(f.remote_field.model, ModelBase)) # Skip when the relationship model wasn't found. - fields = (f for f in fields if isinstance(f.rel.through, ModelBase)) + fields = (f for f in fields if isinstance(f.remote_field.through, ModelBase)) for f in fields: - signature = (f.rel.to, cls, f.rel.through) + signature = (f.remote_field.model, cls, f.remote_field.through) if signature in seen_intermediary_signatures: errors.append( checks.Error( "The model has two many-to-many relations through " "the intermediate model '%s.%s'." % ( - f.rel.through._meta.app_label, - f.rel.through._meta.object_name + f.remote_field.through._meta.app_label, + f.remote_field.through._meta.object_name ), hint=None, obj=cls, @@ -1448,7 +1448,7 @@ class Model(six.with_metaclass(ModelBase)): ) ) else: - if isinstance(field.rel, models.ManyToManyRel): + if isinstance(field.remote_field, models.ManyToManyRel): errors.append( checks.Error( "'%s' refers to a ManyToManyField '%s', but " @@ -1595,7 +1595,7 @@ class Model(six.with_metaclass(ModelBase)): for f in cls._meta.local_many_to_many: # Check if auto-generated name for the M2M field is too long # for the database. - for m2m in f.rel.through._meta.local_fields: + for m2m in f.remote_field.through._meta.local_fields: _, rel_name = m2m.get_attname_column() if (m2m.db_column is None and rel_name is not None and len(rel_name) > allowed_len): @@ -1624,7 +1624,7 @@ class Model(six.with_metaclass(ModelBase)): def method_set_order(ordered_obj, self, id_list, using=None): if using is None: using = DEFAULT_DB_ALIAS - rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name) + rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.remote_field.field_name) order_name = ordered_obj._meta.order_with_respect_to.name # FIXME: It would be nice if there was an "update many" version of update # for situations like this. @@ -1634,7 +1634,7 @@ def method_set_order(ordered_obj, self, id_list, using=None): def method_get_order(ordered_obj, self): - rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name) + rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.remote_field.field_name) order_name = ordered_obj._meta.order_with_respect_to.name pk_name = ordered_obj._meta.pk.name return [r[pk_name] for r in diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index 0adfb7f1593..8bfba43fac6 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -14,7 +14,7 @@ class ProtectedError(IntegrityError): def CASCADE(collector, field, sub_objs, using): - collector.collect(sub_objs, source=field.rel.to, + collector.collect(sub_objs, source=field.remote_field.model, source_attr=field.name, nullable=field.null) if field.null and not connections[using].features.can_defer_constraint_checks: collector.add_field_update(field, None, sub_objs) @@ -23,7 +23,7 @@ def CASCADE(collector, field, sub_objs, using): def PROTECT(collector, field, sub_objs, using): raise ProtectedError("Cannot delete some instances of model '%s' because " "they are referenced through a protected foreign key: '%s.%s'" % ( - field.rel.to.__name__, sub_objs[0].__class__.__name__, field.name + field.remote_field.model.__name__, sub_objs[0].__class__.__name__, field.name ), sub_objs ) @@ -136,7 +136,7 @@ class Collector(object): skipping parent -> child -> parent chain preventing fast delete of the child. """ - if from_field and from_field.rel.on_delete is not CASCADE: + if from_field and from_field.remote_field.on_delete is not CASCADE: return False if not (hasattr(objs, 'model') and hasattr(objs, '_raw_delete')): return False @@ -153,7 +153,7 @@ class Collector(object): # Foreign keys pointing to this model, both from m2m and other # models. for related in get_candidate_relations_to_delete(opts): - if related.field.rel.on_delete is not DO_NOTHING: + if related.field.remote_field.on_delete is not DO_NOTHING: return False for field in model._meta.virtual_fields: if hasattr(field, 'bulk_related_objects'): @@ -214,14 +214,13 @@ class Collector(object): # object instance. parent_objs = [getattr(obj, ptr.name) for obj in new_objs] self.collect(parent_objs, source=model, - source_attr=ptr.rel.related_name, + source_attr=ptr.remote_field.related_name, collect_related=False, reverse_dependency=True) - if collect_related: for related in get_candidate_relations_to_delete(model._meta): field = related.field - if field.rel.on_delete == DO_NOTHING: + if field.remote_field.on_delete == DO_NOTHING: continue batches = self.get_del_batches(new_objs, field) for batch in batches: @@ -229,14 +228,14 @@ class Collector(object): if self.can_fast_delete(sub_objs, from_field=field): self.fast_deletes.append(sub_objs) elif sub_objs: - field.rel.on_delete(self, field, sub_objs, self.using) + field.remote_field.on_delete(self, field, sub_objs, self.using) for field in model._meta.virtual_fields: if hasattr(field, 'bulk_related_objects'): # Its something like generic foreign key. sub_objs = field.bulk_related_objects(new_objs, self.using) self.collect(sub_objs, source=model, - source_attr=field.rel.related_name, + source_attr=field.remote_field.related_name, nullable=True) def related_objects(self, related, objs): diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 6ee1185c90a..b305330f3b6 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -23,6 +23,7 @@ from django.utils.duration import duration_string from django.utils.functional import cached_property, curry, total_ordering, Promise from django.utils.text import capfirst from django.utils import timezone +from django.utils.deprecation import RemovedInDjango21Warning from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import (smart_text, force_text, force_bytes, python_2_unicode_compatible) @@ -146,8 +147,8 @@ class Field(RegisterLookupMixin): self.primary_key = primary_key self.max_length, self._unique = max_length, unique self.blank, self.null = blank, null - self.rel = rel - self.is_relation = self.rel is not None + self.remote_field = rel + self.is_relation = self.remote_field is not None self.default = default self.editable = editable self.serialize = serialize @@ -240,6 +241,13 @@ class Field(RegisterLookupMixin): else: return [] + @property + def rel(self): + warnings.warn( + "Usage of field.rel has been deprecated. Use field.remote_field instead.", + RemovedInDjango21Warning, 2) + return self.remote_field + def _check_choices(self): if self.choices: if (isinstance(self.choices, six.string_types) or @@ -468,10 +476,10 @@ class Field(RegisterLookupMixin): # We don't have to deepcopy very much here, since most things are not # intended to be altered after initial creation. obj = copy.copy(self) - if self.rel: - obj.rel = copy.copy(self.rel) - if hasattr(self.rel, 'field') and self.rel.field is self: - obj.rel.field = obj + if self.remote_field: + obj.remote_field = copy.copy(self.remote_field) + if hasattr(self.remote_field, 'field') and self.remote_field.field is self: + obj.remote_field.field = obj memodict[id(self)] = obj return obj @@ -811,10 +819,10 @@ class Field(RegisterLookupMixin): not blank_defined else []) if self.choices: return first_choice + choices - rel_model = self.rel.to + rel_model = self.remote_field.model limit_choices_to = limit_choices_to or self.get_limit_choices_to() - if hasattr(self.rel, 'get_related_field'): - lst = [(getattr(x, self.rel.get_related_field().attname), + if hasattr(self.remote_field, 'get_related_field'): + lst = [(getattr(x, self.remote_field.get_related_field().attname), smart_text(x)) for x in rel_model._default_manager.complex_filter( limit_choices_to)] diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index e4a176b9215..9e6bbaaf29c 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -22,7 +22,9 @@ from django.db.models.fields.related_lookups import ( from django.db.models.query import QuerySet from django.db.models.query_utils import PathInfo from django.utils import six -from django.utils.deprecation import RemovedInDjango20Warning +from django.utils.deprecation import ( + RemovedInDjango20Warning, RemovedInDjango21Warning, +) from django.utils.encoding import force_text, smart_text from django.utils.functional import cached_property, curry from django.utils.translation import ugettext_lazy as _ @@ -116,7 +118,7 @@ class RelatedField(Field): def related_model(self): # Can't cache this property until all the models are loaded. apps.check_models_ready() - return self.rel.to + return self.remote_field.model def check(self, **kwargs): errors = super(RelatedField, self).check(**kwargs) @@ -129,7 +131,7 @@ class RelatedField(Field): def _check_related_name_is_valid(self): import re import keyword - related_name = self.rel.related_name + related_name = self.remote_field.related_name is_valid_id = (related_name and re.match('^[a-zA-Z_][a-zA-Z0-9_]*$', related_name) and not keyword.iskeyword(related_name)) @@ -137,7 +139,7 @@ class RelatedField(Field): return [ checks.Error( "The name '%s' is invalid related_name for field %s.%s" % - (self.rel.related_name, self.model._meta.object_name, + (self.remote_field.related_name, self.model._meta.object_name, self.name), hint="Related name must be a valid Python identifier or end with a '+'", obj=self, @@ -147,10 +149,10 @@ class RelatedField(Field): return [] def _check_relation_model_exists(self): - rel_is_missing = self.rel.to not in apps.get_models() - rel_is_string = isinstance(self.rel.to, six.string_types) - model_name = self.rel.to if rel_is_string else self.rel.to._meta.object_name - if rel_is_missing and (rel_is_string or not self.rel.to._meta.swapped): + rel_is_missing = self.remote_field.model not in apps.get_models() + rel_is_string = isinstance(self.remote_field.model, six.string_types) + model_name = self.remote_field.model if rel_is_string else self.remote_field.model._meta.object_name + if rel_is_missing and (rel_is_string or not self.remote_field.model._meta.swapped): return [ checks.Error( ("Field defines a relation with model '%s', which " @@ -163,18 +165,18 @@ class RelatedField(Field): return [] def _check_referencing_to_swapped_model(self): - if (self.rel.to not in apps.get_models() and - not isinstance(self.rel.to, six.string_types) and - self.rel.to._meta.swapped): + if (self.remote_field.model not in apps.get_models() and + not isinstance(self.remote_field.model, six.string_types) and + self.remote_field.model._meta.swapped): model = "%s.%s" % ( - self.rel.to._meta.app_label, - self.rel.to._meta.object_name + self.remote_field.model._meta.app_label, + self.remote_field.model._meta.object_name ) return [ checks.Error( ("Field defines a relation with the model '%s', " "which has been swapped out.") % model, - hint="Update the relation to point at 'settings.%s'." % self.rel.to._meta.swappable, + hint="Update the relation to point at 'settings.%s'." % self.remote_field.model._meta.swappable, obj=self, id='fields.E301', ) @@ -190,20 +192,15 @@ class RelatedField(Field): errors = [] opts = self.model._meta - # `f.rel.to` may be a string instead of a model. Skip if model name is + # `f.remote_field.model` may be a string instead of a model. Skip if model name is # not resolved. - if not isinstance(self.rel.to, ModelBase): + if not isinstance(self.remote_field.model, ModelBase): return [] # If the field doesn't install backward relation on the target model (so # `is_hidden` returns True), then there are no clashes to check and we # can skip these fields. - if self.rel.is_hidden(): - return [] - - try: - self.rel - except AttributeError: + if self.remote_field.is_hidden(): return [] # Consider that we are checking field `Model.foreign` and the models @@ -217,9 +214,9 @@ class RelatedField(Field): # foreign = models.ForeignKey(Target) # m2m = models.ManyToManyField(Target) - rel_opts = self.rel.to._meta + rel_opts = self.remote_field.model._meta # rel_opts.object_name == "Target" - rel_name = self.rel.get_accessor_name() # i. e. "model_set" + rel_name = self.remote_field.get_accessor_name() # i. e. "model_set" rel_query_name = self.related_query_name() # i. e. "model" field_name = "%s.%s" % (opts.object_name, self.name) # i. e. "Model.field" @@ -300,16 +297,16 @@ class RelatedField(Field): sup.contribute_to_class(cls, name, virtual_only=virtual_only) if not cls._meta.abstract: - if self.rel.related_name: - related_name = force_text(self.rel.related_name) % { + if self.remote_field.related_name: + related_name = force_text(self.remote_field.related_name) % { 'class': cls.__name__.lower(), 'app_label': cls._meta.app_label.lower() } - self.rel.related_name = related_name - other = self.rel.to + self.remote_field.related_name = related_name + other = self.remote_field.model if isinstance(other, six.string_types) or other._meta.pk is None: def resolve_related_class(field, model, cls): - field.rel.to = model + field.remote_field.model = model field.do_related_class(model, cls) add_lazy_relation(cls, self, other, resolve_related_class) else: @@ -323,12 +320,12 @@ class RelatedField(Field): """ if self.swappable: # Work out string form of "to" - if isinstance(self.rel.to, six.string_types): - to_string = self.rel.to + if isinstance(self.remote_field.model, six.string_types): + to_string = self.remote_field.model else: to_string = "%s.%s" % ( - self.rel.to._meta.app_label, - self.rel.to._meta.object_name, + self.remote_field.model._meta.app_label, + self.remote_field.model._meta.object_name, ) # See if anything swapped/swappable matches for model in apps.get_models(include_swapped=True): @@ -340,22 +337,22 @@ class RelatedField(Field): return None def set_attributes_from_rel(self): - self.name = self.name or (self.rel.to._meta.model_name + '_' + self.rel.to._meta.pk.name) + self.name = self.name or (self.remote_field.model._meta.model_name + '_' + self.remote_field.model._meta.pk.name) if self.verbose_name is None: - self.verbose_name = self.rel.to._meta.verbose_name - self.rel.set_field_name() + self.verbose_name = self.remote_field.model._meta.verbose_name + self.remote_field.set_field_name() @property def related(self): warnings.warn( - "Usage of field.related has been deprecated. Use field.rel instead.", + "Usage of field.related has been deprecated. Use field.remote_field instead.", RemovedInDjango20Warning, 2) - return self.rel + return self.remote_field def do_related_class(self, other, cls): self.set_attributes_from_rel() if not cls._meta.abstract: - self.contribute_to_related_class(other, self.rel) + self.contribute_to_related_class(other, self.remote_field) def get_limit_choices_to(self): """ @@ -364,9 +361,9 @@ class RelatedField(Field): If it is a callable, it will be invoked and the result will be returned. """ - if callable(self.rel.limit_choices_to): - return self.rel.limit_choices_to() - return self.rel.limit_choices_to + if callable(self.remote_field.limit_choices_to): + return self.remote_field.limit_choices_to() + return self.remote_field.limit_choices_to def formfield(self, **kwargs): """ @@ -377,11 +374,11 @@ class RelatedField(Field): being constructed. """ defaults = {} - if hasattr(self.rel, 'get_related_field'): + if hasattr(self.remote_field, 'get_related_field'): # If this is a callable, do not invoke it here. Just pass # it in the defaults for when the form class will later be # instantiated. - limit_choices_to = self.rel.limit_choices_to + limit_choices_to = self.remote_field.limit_choices_to defaults.update({ 'limit_choices_to': limit_choices_to, }) @@ -393,7 +390,19 @@ class RelatedField(Field): Define the name that can be used to identify this related object in a table-spanning query. """ - return self.rel.related_query_name or self.rel.related_name or self.opts.model_name + return self.remote_field.related_query_name or self.remote_field.related_name or self.opts.model_name + + @property + def target_field(self): + """ + When filtering against this relation, returns the field on the remote + model against which the filtering should happen. + """ + target_fields = self.get_path_info()[-1].target_fields + if len(target_fields) > 1: + raise exceptions.FieldError( + "The relation has multiple target fields, but only single target field was asked for") + return target_fields[0] class SingleRelatedObjectDescriptor(object): @@ -553,11 +562,11 @@ class ReverseSingleRelatedObjectDescriptor(object): @cached_property def RelatedObjectDoesNotExist(self): # The exception can't be created at initialization time since the - # related model might not be resolved yet; `rel.to` might still be + # related model might not be resolved yet; `rel.model` might still be # a string model reference. return type( str('RelatedObjectDoesNotExist'), - (self.field.rel.to.DoesNotExist, AttributeError), + (self.field.remote_field.model.DoesNotExist, AttributeError), {} ) @@ -565,11 +574,11 @@ class ReverseSingleRelatedObjectDescriptor(object): return hasattr(instance, self.cache_name) def get_queryset(self, **hints): - manager = self.field.rel.to._default_manager + manager = self.field.remote_field.model._default_manager # If the related manager indicates that it should be used for # related fields, respect that. if not getattr(manager, 'use_for_related_fields', False): - manager = self.field.rel.to._base_manager + manager = self.field.remote_field.model._base_manager return manager.db_manager(hints=hints).all() def get_prefetch_queryset(self, instances, queryset=None): @@ -588,7 +597,7 @@ class ReverseSingleRelatedObjectDescriptor(object): # (related_name ends with a '+'). Refs #21410. # The check for len(...) == 1 is a special case that allows the query # to be join-less and smaller. Refs #21760. - if self.field.rel.is_hidden() or len(self.field.foreign_related_fields) == 1: + if self.field.remote_field.is_hidden() or len(self.field.foreign_related_fields) == 1: query = {'%s__in' % related_field.name: set(instance_attr(inst)[0] for inst in instances)} else: query = {'%s__in' % self.field.related_query_name(): instances} @@ -596,8 +605,8 @@ class ReverseSingleRelatedObjectDescriptor(object): # Since we're going to assign directly in the cache, # we must manage the reverse relation cache manually. - if not self.field.rel.multiple: - rel_obj_cache_name = self.field.rel.get_cache_name() + if not self.field.remote_field.multiple: + rel_obj_cache_name = self.field.remote_field.get_cache_name() for rel_obj in queryset: instance = instances_dict[rel_obj_attr(rel_obj)] setattr(rel_obj, rel_obj_cache_name, instance) @@ -625,8 +634,8 @@ class ReverseSingleRelatedObjectDescriptor(object): qs = qs.filter(extra_filter, **params) # Assuming the database enforces foreign keys, this won't fail. rel_obj = qs.get() - if not self.field.rel.multiple: - setattr(rel_obj, self.field.rel.get_cache_name(), instance) + if not self.field.remote_field.multiple: + setattr(rel_obj, self.field.remote_field.get_cache_name(), instance) setattr(instance, self.cache_name, rel_obj) if rel_obj is None and not self.field.null: raise self.RelatedObjectDoesNotExist( @@ -643,13 +652,13 @@ class ReverseSingleRelatedObjectDescriptor(object): 'Cannot assign None: "%s.%s" does not allow null values.' % (instance._meta.object_name, self.field.name) ) - elif value is not None and not isinstance(value, self.field.rel.to): + elif value is not None and not isinstance(value, self.field.remote_field.model): raise ValueError( 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % ( value, instance._meta.object_name, self.field.name, - self.field.rel.to._meta.object_name, + self.field.remote_field.model._meta.object_name, ) ) elif value is not None: @@ -678,7 +687,7 @@ class ReverseSingleRelatedObjectDescriptor(object): # cache. This cache also might not exist if the related object # hasn't been accessed yet. if related is not None: - setattr(related, self.field.rel.get_cache_name(), None) + setattr(related, self.field.remote_field.get_cache_name(), None) for lh_field, rh_field in self.field.related_fields: setattr(instance, lh_field.attname, None) @@ -690,7 +699,7 @@ class ReverseSingleRelatedObjectDescriptor(object): if not self.field.allow_unsaved_instance_assignment and pk is None: raise ValueError( 'Cannot assign "%r": "%s" instance isn\'t saved in the database.' % - (value, self.field.rel.to._meta.object_name) + (value, self.field.remote_field.model._meta.object_name) ) setattr(instance, lh_field.attname, getattr(value, rh_field.attname)) @@ -698,8 +707,8 @@ class ReverseSingleRelatedObjectDescriptor(object): # object caches now, too. This avoids another db hit if you get the # object you just set. setattr(instance, self.cache_name, value) - if value is not None and not self.field.rel.multiple: - setattr(value, self.field.rel.get_cache_name(), instance) + if value is not None and not self.field.remote_field.multiple: + setattr(value, self.field.remote_field.get_cache_name(), instance) def create_foreign_related_manager(superclass, rel): @@ -809,7 +818,7 @@ def create_foreign_related_manager(superclass, rel): if self.field.get_local_related_value(obj) == val: old_ids.add(obj.pk) else: - raise self.field.rel.to.DoesNotExist("%r is not related to %r." % (obj, self.instance)) + raise self.field.remote_field.model.DoesNotExist("%r is not related to %r." % (obj, self.instance)) self._clear(self.filter(pk__in=old_ids), bulk) remove.alters_data = True @@ -911,7 +920,7 @@ def create_many_related_manager(superclass, rel, reverse): self.instance = instance if not reverse: - self.model = rel.to + self.model = rel.model self.query_field_name = rel.field.related_query_name() self.prefetch_cache_name = rel.field.name self.source_field_name = rel.field.m2m_field_name() @@ -1265,7 +1274,7 @@ class ManyRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor): @cached_property def related_manager_cls(self): - model = self.rel.related_model if self.reverse else self.rel.to + model = self.rel.related_model if self.reverse else self.rel.model return create_many_related_manager( model._default_manager.__class__, self.rel, @@ -1287,10 +1296,14 @@ class ForeignObjectRel(object): editable = False is_relation = True + # Reverse relations are always nullable (Django can't enforce that a + # foreign key on the related model points to this model). + null = True + def __init__(self, field, to, related_name=None, related_query_name=None, limit_choices_to=None, parent_link=False, on_delete=None): self.field = field - self.to = to + self.model = to self.related_name = related_name self.related_query_name = related_query_name self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to @@ -1304,9 +1317,13 @@ class ForeignObjectRel(object): # __init__ as the field doesn't have its model yet. Calling these methods # before field.contribute_to_class() has been called will result in # AttributeError - @cached_property - def model(self): - return self.to + @property + def to(self): + warnings.warn( + "Usage of ForeignObjectRel.to attribute has been deprecated. " + "Use the model attribute instead.", + RemovedInDjango21Warning, 2) + return self.model @cached_property def hidden(self): @@ -1316,6 +1333,21 @@ class ForeignObjectRel(object): def name(self): return self.field.related_query_name() + @property + def remote_field(self): + return self.field + + @property + def target_field(self): + """ + When filtering against this relation, returns the field on the remote + model against which the filtering should happen. + """ + target_fields = self.get_path_info()[-1].target_fields + if len(target_fields) > 1: + raise exceptions.FieldError("Can't use target_field for multicolumn relations.") + return target_fields[0] + @cached_property def related_model(self): if not self.field.model: @@ -1342,13 +1374,6 @@ class ForeignObjectRel(object): def get_prep_lookup(self, lookup_name, value): return self.field.get_prep_lookup(lookup_name, value) - @cached_property - def target_field(self): - target_fields = self.get_path_info().target_fields - if len(target_fields) > 1: - raise RuntimeError("Multicolumn relations do not have a single target_field.") - return target_fields[0] - def get_lookup(self, lookup_name): return self.field.get_lookup(lookup_name) @@ -1419,7 +1444,7 @@ class ForeignObjectRel(object): model = model or self.related_model if self.multiple: # If this is a symmetrical m2m relation on self, there is no reverse accessor. - if self.symmetrical and model == self.to: + if self.symmetrical and model == self.model: return None if self.related_name: return self.related_name @@ -1469,14 +1494,14 @@ class ManyToOneRel(ForeignObjectRel): """ Return the Field in the 'to' object to which this relationship is tied. """ - field = self.to._meta.get_field(self.field_name) + field = self.model._meta.get_field(self.field_name) if not field.concrete: raise FieldDoesNotExist("No related field named '%s'" % self.field_name) return field def set_field_name(self): - self.field_name = self.field_name or self.to._meta.pk.name + self.field_name = self.field_name or self.model._meta.pk.name class OneToOneRel(ManyToOneRel): @@ -1540,8 +1565,8 @@ class ManyToManyRel(ForeignObjectRel): field = opts.get_field(self.through_fields[0]) else: for field in opts.fields: - rel = getattr(field, 'rel', None) - if rel and rel.to == self.to: + rel = getattr(field, 'remote_field', None) + if rel and rel.model == self.model: break return field.foreign_related_fields[0] @@ -1587,7 +1612,7 @@ class ForeignObject(RelatedField): return errors def _check_unique_target(self): - rel_is_string = isinstance(self.rel.to, six.string_types) + rel_is_string = isinstance(self.remote_field.model, six.string_types) if rel_is_string or not self.requires_unique_target: return [] @@ -1596,17 +1621,12 @@ class ForeignObject(RelatedField): except FieldDoesNotExist: return [] - try: - self.rel - except AttributeError: - return [] - has_unique_field = any(rel_field.unique for rel_field in self.foreign_related_fields) if not has_unique_field and len(self.foreign_related_fields) > 1: field_combination = ', '.join("'%s'" % rel_field.name for rel_field in self.foreign_related_fields) - model_name = self.rel.to.__name__ + model_name = self.remote_field.model.__name__ return [ checks.Error( "None of the fields %s on model '%s' have a unique=True constraint." @@ -1618,7 +1638,7 @@ class ForeignObject(RelatedField): ] elif not has_unique_field: field_name = self.foreign_related_fields[0].name - model_name = self.rel.to.__name__ + model_name = self.remote_field.model.__name__ return [ checks.Error( ("'%s.%s' must set unique=True " @@ -1635,19 +1655,19 @@ class ForeignObject(RelatedField): name, path, args, kwargs = super(ForeignObject, self).deconstruct() kwargs['from_fields'] = self.from_fields kwargs['to_fields'] = self.to_fields - if self.rel.related_name is not None: - kwargs['related_name'] = self.rel.related_name - if self.rel.related_query_name is not None: - kwargs['related_query_name'] = self.rel.related_query_name - if self.rel.on_delete != CASCADE: - kwargs['on_delete'] = self.rel.on_delete - if self.rel.parent_link: - kwargs['parent_link'] = self.rel.parent_link + if self.remote_field.related_name is not None: + kwargs['related_name'] = self.remote_field.related_name + if self.remote_field.related_query_name is not None: + kwargs['related_query_name'] = self.remote_field.related_query_name + if self.remote_field.on_delete != CASCADE: + kwargs['on_delete'] = self.remote_field.on_delete + if self.remote_field.parent_link: + kwargs['parent_link'] = self.remote_field.parent_link # Work out string form of "to" - if isinstance(self.rel.to, six.string_types): - kwargs['to'] = self.rel.to + if isinstance(self.remote_field.model, six.string_types): + kwargs['to'] = self.remote_field.model else: - kwargs['to'] = "%s.%s" % (self.rel.to._meta.app_label, self.rel.to._meta.object_name) + kwargs['to'] = "%s.%s" % (self.remote_field.model._meta.app_label, self.remote_field.model._meta.object_name) # If swappable is True, then see if we're actually pointing to the target # of a swap. swappable_setting = self.swappable_setting @@ -1671,16 +1691,16 @@ class ForeignObject(RelatedField): def resolve_related_fields(self): if len(self.from_fields) < 1 or len(self.from_fields) != len(self.to_fields): raise ValueError('Foreign Object from and to fields must be the same non-zero length') - if isinstance(self.rel.to, six.string_types): - raise ValueError('Related model %r cannot be resolved' % self.rel.to) + if isinstance(self.remote_field.model, six.string_types): + raise ValueError('Related model %r cannot be resolved' % self.remote_field.model) related_fields = [] for index in range(len(self.from_fields)): from_field_name = self.from_fields[index] to_field_name = self.to_fields[index] from_field = (self if from_field_name == 'self' else self.opts.get_field(from_field_name)) - to_field = (self.rel.to._meta.pk if to_field_name is None - else self.rel.to._meta.get_field(to_field_name)) + to_field = (self.remote_field.model._meta.pk if to_field_name is None + else self.remote_field.model._meta.get_field(to_field_name)) related_fields.append((from_field, to_field)) return related_fields @@ -1770,7 +1790,7 @@ class ForeignObject(RelatedField): """ Get path from this field to the related model. """ - opts = self.rel.to._meta + opts = self.remote_field.model._meta from_opts = self.model._meta return [PathInfo(from_opts, opts, self.foreign_related_fields, self, False, True)] @@ -1779,8 +1799,8 @@ class ForeignObject(RelatedField): Get path from the related model to this field's model. """ opts = self.model._meta - from_opts = self.rel.to._meta - pathinfos = [PathInfo(from_opts, opts, (opts.pk,), self.rel, not self.unique, False)] + from_opts = self.remote_field.model._meta + pathinfos = [PathInfo(from_opts, opts, (opts.pk,), self.remote_field, not self.unique, False)] return pathinfos def get_lookup(self, lookup_name): @@ -1817,13 +1837,13 @@ class ForeignObject(RelatedField): def contribute_to_related_class(self, cls, related): # Internal FK's - i.e., those with a related name ending with '+' - # and swapped models don't get a related descriptor. - if not self.rel.is_hidden() and not related.related_model._meta.swapped: + if not self.remote_field.is_hidden() and not related.related_model._meta.swapped: setattr(cls, related.get_accessor_name(), self.related_accessor_class(related)) # While 'limit_choices_to' might be a callable, simply pass # it along for later - this is too early because it's still # model load time. - if self.rel.limit_choices_to: - cls._meta.related_fkey_lookups.append(self.rel.limit_choices_to) + if self.remote_field.limit_choices_to: + cls._meta.related_fkey_lookups.append(self.remote_field.limit_choices_to) class ForeignKey(ForeignObject): @@ -1891,7 +1911,7 @@ class ForeignKey(ForeignObject): return errors def _check_on_delete(self): - on_delete = getattr(self.rel, 'on_delete', None) + on_delete = getattr(self.remote_field, 'on_delete', None) if on_delete == SET_NULL and not self.null: return [ checks.Error( @@ -1935,9 +1955,9 @@ class ForeignKey(ForeignObject): if self.db_constraint is not True: kwargs['db_constraint'] = self.db_constraint # Rel needs more work. - to_meta = getattr(self.rel.to, "_meta", None) - if self.rel.field_name and (not to_meta or (to_meta.pk and self.rel.field_name != to_meta.pk.name)): - kwargs['to_field'] = self.rel.field_name + to_meta = getattr(self.remote_field.model, "_meta", None) + if self.remote_field.field_name and (not to_meta or (to_meta.pk and self.remote_field.field_name != to_meta.pk.name)): + kwargs['to_field'] = self.remote_field.field_name return name, path, args, kwargs @property @@ -1949,20 +1969,20 @@ class ForeignKey(ForeignObject): Get path from the related model to this field's model. """ opts = self.model._meta - from_opts = self.rel.to._meta - pathinfos = [PathInfo(from_opts, opts, (opts.pk,), self.rel, not self.unique, False)] + from_opts = self.remote_field.model._meta + pathinfos = [PathInfo(from_opts, opts, (opts.pk,), self.remote_field, not self.unique, False)] return pathinfos def validate(self, value, model_instance): - if self.rel.parent_link: + if self.remote_field.parent_link: return super(ForeignKey, self).validate(value, model_instance) if value is None: return using = router.db_for_read(model_instance.__class__, instance=model_instance) - qs = self.rel.to._default_manager.using(using).filter( - **{self.rel.field_name: value} + qs = self.remote_field.model._default_manager.using(using).filter( + **{self.remote_field.field_name: value} ) qs = qs.complex_filter(self.get_limit_choices_to()) if not qs.exists(): @@ -1970,8 +1990,8 @@ class ForeignKey(ForeignObject): self.error_messages['invalid'], code='invalid', params={ - 'model': self.rel.to._meta.verbose_name, 'pk': value, - 'field': self.rel.field_name, 'value': value, + 'model': self.remote_field.model._meta.verbose_name, 'pk': value, + 'field': self.remote_field.field_name, 'value': value, }, # 'pk' is included for backwards compatibility ) @@ -1986,7 +2006,7 @@ class ForeignKey(ForeignObject): def get_default(self): "Here we check if the default value is an object and return the to_field if so." field_default = super(ForeignKey, self).get_default() - if isinstance(field_default, self.rel.to): + if isinstance(field_default, self.remote_field.model): return getattr(field_default, self.target_field.attname) return field_default @@ -2015,19 +2035,19 @@ class ForeignKey(ForeignObject): def contribute_to_related_class(self, cls, related): super(ForeignKey, self).contribute_to_related_class(cls, related) - if self.rel.field_name is None: - self.rel.field_name = cls._meta.pk.name + if self.remote_field.field_name is None: + self.remote_field.field_name = cls._meta.pk.name def formfield(self, **kwargs): db = kwargs.pop('using', None) - if isinstance(self.rel.to, six.string_types): + if isinstance(self.remote_field.model, six.string_types): raise ValueError("Cannot create form field for %r yet, because " "its related model %r has not been loaded yet" % - (self.name, self.rel.to)) + (self.name, self.remote_field.model)) defaults = { 'form_class': forms.ModelChoiceField, - 'queryset': self.rel.to._default_manager.using(db), - 'to_field_name': self.rel.field_name, + 'queryset': self.remote_field.model._default_manager.using(db), + 'to_field_name': self.remote_field.field_name, } defaults.update(kwargs) return super(ForeignKey, self).formfield(**defaults) @@ -2095,12 +2115,12 @@ class OneToOneField(ForeignKey): return name, path, args, kwargs def formfield(self, **kwargs): - if self.rel.parent_link: + if self.remote_field.parent_link: return None return super(OneToOneField, self).formfield(**kwargs) def save_form_data(self, instance, data): - if isinstance(data, self.rel.to): + if isinstance(data, self.remote_field.model): setattr(instance, self.name, data) else: setattr(instance, self.attname, data) @@ -2113,23 +2133,23 @@ class OneToOneField(ForeignKey): def create_many_to_many_intermediary_model(field, klass): from django.db import models managed = True - if isinstance(field.rel.to, six.string_types) and field.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT: - to_model = field.rel.to + if isinstance(field.remote_field.model, six.string_types) and field.remote_field.model != RECURSIVE_RELATIONSHIP_CONSTANT: + to_model = field.remote_field.model to = to_model.split('.')[-1] def set_managed(field, model, cls): - field.rel.through._meta.managed = model._meta.managed or cls._meta.managed + field.remote_field.through._meta.managed = model._meta.managed or cls._meta.managed add_lazy_relation(klass, field, to_model, set_managed) - elif isinstance(field.rel.to, six.string_types): + elif isinstance(field.remote_field.model, six.string_types): to = klass._meta.object_name to_model = klass managed = klass._meta.managed else: - to = field.rel.to._meta.object_name - to_model = field.rel.to + to = field.remote_field.model._meta.object_name + to_model = field.remote_field.model managed = klass._meta.managed or to_model._meta.managed name = '%s_%s' % (klass._meta.object_name, field.name) - if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or to == klass._meta.object_name: + if field.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT or to == klass._meta.object_name: from_ = 'from_%s' % to.lower() to = 'to_%s' % to.lower() else: @@ -2154,13 +2174,13 @@ def create_many_to_many_intermediary_model(field, klass): klass, related_name='%s+' % name, db_tablespace=field.db_tablespace, - db_constraint=field.rel.db_constraint, + db_constraint=field.remote_field.db_constraint, ), to: models.ForeignKey( to_model, related_name='%s+' % name, db_tablespace=field.db_tablespace, - db_constraint=field.rel.db_constraint, + db_constraint=field.remote_field.db_constraint, ) }) @@ -2219,11 +2239,14 @@ class ManyToManyField(RelatedField): through_fields=through_fields, db_constraint=db_constraint, ) + self.has_null_arg = 'null' in kwargs super(ManyToManyField, self).__init__(**kwargs) self.db_table = db_table self.swappable = swappable + # Many-to-many fields are always nullable. + self.null = True def check(self, **kwargs): errors = super(ManyToManyField, self).check(**kwargs) @@ -2247,7 +2270,7 @@ class ManyToManyField(RelatedField): def _check_ignored_options(self, **kwargs): warnings = [] - if self.null: + if self.has_null_arg: warnings.append( checks.Warning( 'null has no effect on ManyToManyField.', @@ -2270,15 +2293,15 @@ class ManyToManyField(RelatedField): return warnings def _check_relationship_model(self, from_model=None, **kwargs): - if hasattr(self.rel.through, '_meta'): + if hasattr(self.remote_field.through, '_meta'): qualified_model_name = "%s.%s" % ( - self.rel.through._meta.app_label, self.rel.through.__name__) + self.remote_field.through._meta.app_label, self.remote_field.through.__name__) else: - qualified_model_name = self.rel.through + qualified_model_name = self.remote_field.through errors = [] - if self.rel.through not in apps.get_models(include_auto_created=True): + if self.remote_field.through not in apps.get_models(include_auto_created=True): # The relationship model is not installed. errors.append( checks.Error( @@ -2300,18 +2323,18 @@ class ManyToManyField(RelatedField): ) # Set some useful local variables - to_model = self.rel.to + to_model = self.remote_field.model from_model_name = from_model._meta.object_name if isinstance(to_model, six.string_types): to_model_name = to_model else: to_model_name = to_model._meta.object_name - relationship_model_name = self.rel.through._meta.object_name + relationship_model_name = self.remote_field.through._meta.object_name self_referential = from_model == to_model # Check symmetrical attribute. - if (self_referential and self.rel.symmetrical and - not self.rel.through._meta.auto_created): + if (self_referential and self.remote_field.symmetrical and + not self.remote_field.through._meta.auto_created): errors.append( checks.Error( 'Many-to-many fields with intermediate tables must not be symmetrical.', @@ -2323,10 +2346,10 @@ class ManyToManyField(RelatedField): # Count foreign keys in intermediate model if self_referential: - seen_self = sum(from_model == getattr(field.rel, 'to', None) - for field in self.rel.through._meta.fields) + seen_self = sum(from_model == getattr(field.remote_field, 'model', None) + for field in self.remote_field.through._meta.fields) - if seen_self > 2 and not self.rel.through_fields: + if seen_self > 2 and not self.remote_field.through_fields: errors.append( checks.Error( ("The model is used as an intermediate model by " @@ -2336,19 +2359,19 @@ class ManyToManyField(RelatedField): "through_fields keyword argument.") % (self, from_model_name), hint=("Use through_fields to specify which two " "foreign keys Django should use."), - obj=self.rel.through, + obj=self.remote_field.through, id='fields.E333', ) ) else: # Count foreign keys in relationship model - seen_from = sum(from_model == getattr(field.rel, 'to', None) - for field in self.rel.through._meta.fields) - seen_to = sum(to_model == getattr(field.rel, 'to', None) - for field in self.rel.through._meta.fields) + seen_from = sum(from_model == getattr(field.remote_field, 'model', None) + for field in self.remote_field.through._meta.fields) + seen_to = sum(to_model == getattr(field.remote_field, 'model', None) + for field in self.remote_field.through._meta.fields) - if seen_from > 1 and not self.rel.through_fields: + if seen_from > 1 and not self.remote_field.through_fields: errors.append( checks.Error( ("The model is used as an intermediate model by " @@ -2364,7 +2387,7 @@ class ManyToManyField(RelatedField): ) ) - if seen_to > 1 and not self.rel.through_fields: + if seen_to > 1 and not self.remote_field.through_fields: errors.append( checks.Error( ("The model is used as an intermediate model by " @@ -2388,17 +2411,17 @@ class ManyToManyField(RelatedField): self, from_model_name, to_model_name ), hint=None, - obj=self.rel.through, + obj=self.remote_field.through, id='fields.E336', ) ) # Validate `through_fields`. - if self.rel.through_fields is not None: + if self.remote_field.through_fields is not None: # Validate that we're given an iterable of at least two items # and that none of them is "falsy". - if not (len(self.rel.through_fields) >= 2 and - self.rel.through_fields[0] and self.rel.through_fields[1]): + if not (len(self.remote_field.through_fields) >= 2 and + self.remote_field.through_fields[0] and self.remote_field.through_fields[1]): errors.append( checks.Error( ("Field specifies 'through_fields' but does not " @@ -2422,15 +2445,15 @@ class ManyToManyField(RelatedField): "where the field is attached to." ) - source, through, target = from_model, self.rel.through, self.rel.to - source_field_name, target_field_name = self.rel.through_fields[:2] + source, through, target = from_model, self.remote_field.through, self.remote_field.model + source_field_name, target_field_name = self.remote_field.through_fields[:2] for field_name, related_model in ((source_field_name, source), (target_field_name, target)): possible_field_names = [] for f in through._meta.fields: - if hasattr(f, 'rel') and getattr(f.rel, 'to', None) == related_model: + if hasattr(f, 'remote_field') and getattr(f.remote_field, 'model', None) == related_model: possible_field_names.append(f.name) if possible_field_names: hint = ("Did you mean one of the following foreign " @@ -2452,8 +2475,8 @@ class ManyToManyField(RelatedField): ) ) else: - if not (hasattr(field, 'rel') and - getattr(field.rel, 'to', None) == related_model): + if not (hasattr(field, 'remote_field') and + getattr(field.remote_field, 'model', None) == related_model): errors.append( checks.Error( "'%s.%s' is not a foreign key to '%s'." % ( @@ -2470,24 +2493,31 @@ class ManyToManyField(RelatedField): def deconstruct(self): name, path, args, kwargs = super(ManyToManyField, self).deconstruct() # Handle the simpler arguments. + del kwargs["null"] if self.db_table is not None: kwargs['db_table'] = self.db_table - if self.rel.db_constraint is not True: - kwargs['db_constraint'] = self.rel.db_constraint - if self.rel.related_name is not None: - kwargs['related_name'] = self.rel.related_name - if self.rel.related_query_name is not None: - kwargs['related_query_name'] = self.rel.related_query_name + if self.remote_field.db_constraint is not True: + kwargs['db_constraint'] = self.remote_field.db_constraint + if self.remote_field.related_name is not None: + kwargs['related_name'] = self.remote_field.related_name + if self.remote_field.related_query_name is not None: + kwargs['related_query_name'] = self.remote_field.related_query_name # Rel needs more work. - if isinstance(self.rel.to, six.string_types): - kwargs['to'] = self.rel.to + if isinstance(self.remote_field.model, six.string_types): + kwargs['to'] = self.remote_field.model else: - kwargs['to'] = "%s.%s" % (self.rel.to._meta.app_label, self.rel.to._meta.object_name) - if getattr(self.rel, 'through', None) is not None: - if isinstance(self.rel.through, six.string_types): - kwargs['through'] = self.rel.through - elif not self.rel.through._meta.auto_created: - kwargs['through'] = "%s.%s" % (self.rel.through._meta.app_label, self.rel.through._meta.object_name) + kwargs['to'] = "%s.%s" % ( + self.remote_field.model._meta.app_label, + self.remote_field.model._meta.object_name, + ) + if getattr(self.remote_field, 'through', None) is not None: + if isinstance(self.remote_field.through, six.string_types): + kwargs['through'] = self.remote_field.through + elif not self.remote_field.through._meta.auto_created: + kwargs['through'] = "%s.%s" % ( + self.remote_field.through._meta.app_label, + self.remote_field.through._meta.object_name, + ) # If swappable is True, then see if we're actually pointing to the target # of a swap. swappable_setting = self.swappable_setting @@ -2513,7 +2543,7 @@ class ManyToManyField(RelatedField): Called by both direct and indirect m2m traversal. """ pathinfos = [] - int_model = self.rel.through + int_model = self.remote_field.through linkfield1 = int_model._meta.get_field(self.m2m_field_name()) linkfield2 = int_model._meta.get_field(self.m2m_reverse_field_name()) if direct: @@ -2540,8 +2570,8 @@ class ManyToManyField(RelatedField): Function that can be curried to provide the m2m table name for this relation. """ - if self.rel.through is not None: - return self.rel.through._meta.db_table + if self.remote_field.through is not None: + return self.remote_field.through._meta.db_table elif self.db_table: return self.db_table else: @@ -2556,12 +2586,12 @@ class ManyToManyField(RelatedField): cache_attr = '_m2m_%s_cache' % attr if hasattr(self, cache_attr): return getattr(self, cache_attr) - if self.rel.through_fields is not None: - link_field_name = self.rel.through_fields[0] + if self.remote_field.through_fields is not None: + link_field_name = self.remote_field.through_fields[0] else: link_field_name = None - for f in self.rel.through._meta.fields: - if (f.is_relation and f.rel.to == related.related_model and + for f in self.remote_field.through._meta.fields: + if (f.is_relation and f.remote_field.model == related.related_model and (link_field_name is None or link_field_name == f.name)): setattr(self, cache_attr, getattr(f, attr)) return getattr(self, cache_attr) @@ -2575,12 +2605,12 @@ class ManyToManyField(RelatedField): if hasattr(self, cache_attr): return getattr(self, cache_attr) found = False - if self.rel.through_fields is not None: - link_field_name = self.rel.through_fields[1] + if self.remote_field.through_fields is not None: + link_field_name = self.remote_field.through_fields[1] else: link_field_name = None - for f in self.rel.through._meta.fields: - if f.is_relation and f.rel.to == related.model: + for f in self.remote_field.through._meta.fields: + if f.is_relation and f.remote_field.model == related.model: if link_field_name is None and related.related_model == related.model: # If this is an m2m-intermediate to self, # the first foreign key you find will be @@ -2617,8 +2647,9 @@ class ManyToManyField(RelatedField): # specify *what* on my non-reversible relation?!"), so we set it up # automatically. The funky name reduces the chance of an accidental # clash. - if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name): - self.rel.related_name = "%s_rel_+" % name + if self.remote_field.symmetrical and ( + self.remote_field.model == "self" or self.remote_field.model == cls._meta.object_name): + self.remote_field.related_name = "%s_rel_+" % name super(ManyToManyField, self).contribute_to_class(cls, name, **kwargs) @@ -2626,27 +2657,27 @@ class ManyToManyField(RelatedField): # 1) There is a manually specified intermediate, or # 2) The class owning the m2m field is abstract. # 3) The class owning the m2m field has been swapped out. - if not self.rel.through and not cls._meta.abstract and not cls._meta.swapped: - self.rel.through = create_many_to_many_intermediary_model(self, cls) + if not self.remote_field.through and not cls._meta.abstract and not cls._meta.swapped: + self.remote_field.through = create_many_to_many_intermediary_model(self, cls) # Add the descriptor for the m2m relation. - setattr(cls, self.name, ManyRelatedObjectsDescriptor(self.rel, reverse=False)) + setattr(cls, self.name, ManyRelatedObjectsDescriptor(self.remote_field, reverse=False)) # Set up the accessor for the m2m table name for the relation. self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) # Populate some necessary rel arguments so that cross-app relations # work correctly. - if not cls._meta.abstract and isinstance(self.rel.through, six.string_types): + if not cls._meta.abstract and isinstance(self.remote_field.through, six.string_types): def resolve_through_model(field, model, cls): - field.rel.through = model - add_lazy_relation(cls, self, self.rel.through, resolve_through_model) + field.remote_field.through = model + add_lazy_relation(cls, self, self.remote_field.through, resolve_through_model) def contribute_to_related_class(self, cls, related): # Internal M2Ms (i.e., those with a related name ending with '+') # and swapped models don't get a related descriptor. - if not self.rel.is_hidden() and not related.related_model._meta.swapped: - setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(self.rel, reverse=True)) + if not self.remote_field.is_hidden() and not related.related_model._meta.swapped: + setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(self.remote_field, reverse=True)) # Set up the accessors for the column names on the m2m table. self.m2m_column_name = curry(self._get_m2m_attr, related, 'column') @@ -2655,9 +2686,9 @@ class ManyToManyField(RelatedField): self.m2m_field_name = curry(self._get_m2m_attr, related, 'name') self.m2m_reverse_field_name = curry(self._get_m2m_reverse_attr, related, 'name') - get_m2m_rel = curry(self._get_m2m_attr, related, 'rel') + get_m2m_rel = curry(self._get_m2m_attr, related, 'remote_field') self.m2m_target_field_name = lambda: get_m2m_rel().field_name - get_m2m_reverse_rel = curry(self._get_m2m_reverse_attr, related, 'rel') + get_m2m_reverse_rel = curry(self._get_m2m_reverse_attr, related, 'remote_field') self.m2m_reverse_target_field_name = lambda: get_m2m_reverse_rel().field_name def set_attributes_from_rel(self): @@ -2676,7 +2707,7 @@ class ManyToManyField(RelatedField): db = kwargs.pop('using', None) defaults = { 'form_class': forms.ModelMultipleChoiceField, - 'queryset': self.rel.to._default_manager.using(db), + 'queryset': self.remote_field.model._default_manager.using(db), } defaults.update(kwargs) # If initial is passed in, it's a list of related objects, but the diff --git a/django/db/models/fields/related_lookups.py b/django/db/models/fields/related_lookups.py index b689c9928e0..1588128ed11 100644 --- a/django/db/models/fields/related_lookups.py +++ b/django/db/models/fields/related_lookups.py @@ -26,8 +26,8 @@ def get_normalized_value(value, lhs): # Account for one-to-one relations when sent a different model sources = lhs.output_field.get_path_info()[-1].target_fields for source in sources: - while not isinstance(value, source.model) and source.rel: - source = source.rel.to._meta.get_field(source.rel.field_name) + while not isinstance(value, source.model) and source.remote_field: + source = source.remote_field.model._meta.get_field(source.remote_field.field_name) value_list.append(getattr(value, source.attname)) return tuple(value_list) if not isinstance(value, tuple): diff --git a/django/db/models/options.py b/django/db/models/options.py index 4a2ab1036f0..91b6790b84e 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -308,9 +308,9 @@ class Options(object): # ideally, we'd just ask for field.related_model. However, related_model # is a cached property, and all the models haven't been loaded yet, so # we need to make sure we don't cache a string reference. - if field.is_relation and hasattr(field.rel, 'to') and field.rel.to: + if field.is_relation and hasattr(field.remote_field, 'model') and field.remote_field.model: try: - field.rel.to._meta._expire_cache(forward=False) + field.remote_field.model._meta._expire_cache(forward=False) except AttributeError: pass self._expire_cache() @@ -393,7 +393,7 @@ class Options(object): is_not_an_m2m_field = lambda f: not (f.is_relation and f.many_to_many) is_not_a_generic_relation = lambda f: not (f.is_relation and f.one_to_many) is_not_a_generic_foreign_key = lambda f: not ( - f.is_relation and f.many_to_one and not (hasattr(f.rel, 'to') and f.rel.to) + f.is_relation and f.many_to_one and not (hasattr(f.remote_field, 'model') and f.remote_field.model) ) return make_immutable_fields_list( "fields", @@ -592,8 +592,8 @@ class Options(object): children = chain.from_iterable(c._relation_tree for c in self.concrete_model._meta.proxied_children if c is not self) - relations = (f.rel for f in children - if include_hidden or not f.rel.field.rel.is_hidden()) + relations = (f.remote_field for f in children + if include_hidden or not f.remote_field.field.remote_field.is_hidden()) fields = chain(fields, relations) return list(fields) @@ -690,8 +690,8 @@ class Options(object): if f.is_relation and f.related_model is not None ) for f in fields_with_relations: - if not isinstance(f.rel.to, six.string_types): - related_objects_graph[f.rel.to._meta].append(f) + if not isinstance(f.remote_field.model, six.string_types): + related_objects_graph[f.remote_field.model._meta].append(f) for model in all_models: # Set the relation_tree using the internal __dict__. In this way @@ -804,8 +804,8 @@ class Options(object): for field in all_fields: # If hidden fields should be included or the relation is not # intentionally hidden, add to the fields dict. - if include_hidden or not field.rel.hidden: - fields.append(field.rel) + if include_hidden or not field.remote_field.hidden: + fields.append(field.remote_field) if forward: fields.extend( diff --git a/django/db/models/query.py b/django/db/models/query.py index b50650b057b..5f455b12beb 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1645,12 +1645,12 @@ class RelatedPopulator(object): reverse = klass_info['reverse'] self.reverse_cache_name = None if reverse: - self.cache_name = field.rel.get_cache_name() + self.cache_name = field.remote_field.get_cache_name() self.reverse_cache_name = field.get_cache_name() else: self.cache_name = field.get_cache_name() if field.unique: - self.reverse_cache_name = field.rel.get_cache_name() + self.reverse_cache_name = field.remote_field.get_cache_name() def get_deferred_cls(self, klass_info, init_list): model_cls = klass_info['model'] diff --git a/django/db/models/query_utils.py b/django/db/models/query_utils.py index 35e3fe58bce..d8bf9b8e677 100644 --- a/django/db/models/query_utils.py +++ b/django/db/models/query_utils.py @@ -181,9 +181,9 @@ def select_related_descend(field, restricted, requested, load_fields, reverse=Fa * load_fields - the set of fields to be loaded on this model * reverse - boolean, True if we are checking a reverse select related """ - if not field.rel: + if not field.remote_field: return False - if field.rel.parent_link and not reverse: + if field.remote_field.parent_link and not reverse: return False if restricted: if reverse and field.related_query_name() not in requested: diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index d54c1bc5e42..002bc058243 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -676,7 +676,7 @@ class SQLCompiler(object): only_load.get(field_model)): continue klass_info = { - 'model': f.rel.to, + 'model': f.remote_field.model, 'field': f, 'reverse': False, 'from_parent': False, @@ -686,13 +686,13 @@ class SQLCompiler(object): _, _, _, joins, _ = self.query.setup_joins( [f.name], opts, root_alias) alias = joins[-1] - columns = self.get_default_columns(start_alias=alias, opts=f.rel.to._meta) + columns = self.get_default_columns(start_alias=alias, opts=f.remote_field.model._meta) for col in columns: select_fields.append(len(select)) select.append((col, None)) klass_info['select_fields'] = select_fields next_klass_infos = self.get_related_selections( - select, f.rel.to._meta, alias, cur_depth + 1, next, restricted) + select, f.remote_field.model._meta, alias, cur_depth + 1, next, restricted) get_related_klass_infos(klass_info, next_klass_infos) if restricted: @@ -1003,7 +1003,7 @@ class SQLUpdateCompiler(SQLCompiler): if val.contains_aggregate: raise FieldError("Aggregate functions are not allowed in this query") elif hasattr(val, 'prepare_database_save'): - if field.rel: + if field.remote_field: val = val.prepare_database_save(field) else: raise TypeError("Database is trying to update a relational field " diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index c041ef0b071..69178680ee8 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -608,7 +608,7 @@ class Query(object): if is_reverse_o2o(source): cur_model = source.related_model else: - cur_model = source.rel.to + cur_model = source.remote_field.model opts = cur_model._meta # Even if we're "just passing through" this model, we must add # both the current model's pk and the related reference field @@ -1303,7 +1303,7 @@ class Query(object): opts = int_model._meta else: final_field = opts.parents[int_model] - targets = (final_field.rel.get_related_field(),) + targets = (final_field.remote_field.get_related_field(),) opts = int_model._meta path.append(PathInfo(final_field.model._meta, opts, targets, final_field, False, True)) cur_names_with_path[1].append( diff --git a/django/forms/models.py b/django/forms/models.py index 1a0076eaee2..59f9d501d51 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -589,8 +589,8 @@ class BaseModelFormSet(BaseFormSet): If the field is a related field, fetch the concrete field's (that is, the ultimate pointed-to field's) to_python. """ - while field.rel is not None: - field = field.rel.get_related_field() + while field.remote_field is not None: + field = field.remote_field.get_related_field() return field.to_python def _construct_form(self, i, **kwargs): @@ -797,7 +797,7 @@ class BaseModelFormSet(BaseFormSet): def pk_is_not_editable(pk): return ((not pk.editable) or (pk.auto_created or isinstance(pk, AutoField)) - or (pk.rel and pk.rel.parent_link and pk_is_not_editable(pk.rel.to._meta.pk))) + or (pk.remote_field and pk.remote_field.parent_link and pk_is_not_editable(pk.remote_field.model._meta.pk))) if pk_is_not_editable(pk) or pk.name not in form.fields: if form.is_bound: # If we're adding the related instance, ignore its primary key @@ -813,7 +813,7 @@ class BaseModelFormSet(BaseFormSet): except IndexError: pk_value = None if isinstance(pk, OneToOneField) or isinstance(pk, ForeignKey): - qs = pk.rel.to._default_manager.get_queryset() + qs = pk.remote_field.model._default_manager.get_queryset() else: qs = self.model._default_manager.get_queryset() qs = qs.using(form.instance._state.db) @@ -863,7 +863,7 @@ class BaseInlineFormSet(BaseModelFormSet): def __init__(self, data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None, **kwargs): if instance is None: - self.instance = self.fk.rel.to() + self.instance = self.fk.remote_field.model() else: self.instance = instance self.save_as_new = save_as_new @@ -893,15 +893,15 @@ class BaseInlineFormSet(BaseModelFormSet): # Set the fk value here so that the form can do its validation. fk_value = self.instance.pk - if self.fk.rel.field_name != self.fk.rel.to._meta.pk.name: - fk_value = getattr(self.instance, self.fk.rel.field_name) + if self.fk.remote_field.field_name != self.fk.remote_field.model._meta.pk.name: + fk_value = getattr(self.instance, self.fk.remote_field.field_name) fk_value = getattr(fk_value, 'pk', fk_value) setattr(form.instance, self.fk.get_attname(), fk_value) return form @classmethod def get_default_prefix(cls): - return cls.fk.rel.get_accessor_name(model=cls.model).replace('+', '') + return cls.fk.remote_field.get_accessor_name(model=cls.model).replace('+', '') def save_new(self, form, commit=True): # Ensure the latest copy of the related instance is present on each @@ -911,7 +911,7 @@ class BaseInlineFormSet(BaseModelFormSet): # Use commit=False so we can assign the parent key afterwards, then # save the object. obj = form.save(commit=False) - pk_value = getattr(self.instance, self.fk.rel.field_name) + pk_value = getattr(self.instance, self.fk.remote_field.field_name) setattr(obj, self.fk.get_attname(), getattr(pk_value, 'pk', pk_value)) if commit: obj.save() @@ -932,8 +932,8 @@ class BaseInlineFormSet(BaseModelFormSet): kwargs = { 'label': getattr(form.fields.get(name), 'label', capfirst(self.fk.verbose_name)) } - if self.fk.rel.field_name != self.fk.rel.to._meta.pk.name: - kwargs['to_field'] = self.fk.rel.field_name + if self.fk.remote_field.field_name != self.fk.remote_field.model._meta.pk.name: + kwargs['to_field'] = self.fk.remote_field.field_name # If we're adding a new object, ignore a parent's auto-generated pk # as it will be regenerated on the save request. @@ -970,8 +970,8 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False): if len(fks_to_parent) == 1: fk = fks_to_parent[0] if not isinstance(fk, ForeignKey) or \ - (fk.rel.to != parent_model and - fk.rel.to not in parent_model._meta.get_parent_list()): + (fk.remote_field.model != parent_model and + fk.remote_field.model not in parent_model._meta.get_parent_list()): raise ValueError( "fk_name '%s' is not a ForeignKey to '%s.%s'." % (fk_name, parent_model._meta.app_label, parent_model._meta.object_name)) @@ -984,8 +984,8 @@ def _get_foreign_key(parent_model, model, fk_name=None, can_fail=False): fks_to_parent = [ f for f in opts.fields if isinstance(f, ForeignKey) - and (f.rel.to == parent_model - or f.rel.to in parent_model._meta.get_parent_list()) + and (f.remote_field.model == parent_model + or f.remote_field.model in parent_model._meta.get_parent_list()) ] if len(fks_to_parent) == 1: fk = fks_to_parent[0] diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index db351ede16c..abce521d757 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -26,6 +26,10 @@ details on these changes. compatibility layer which allows absolute URLs to be considered equal to relative ones when the path is identical will also be removed. +* ``Field.rel`` will be removed. + +* ``Field.remote_field.to`` attribute will be removed. + .. _deprecation-removed-in-2.0: 2.0 diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 10a4db55182..9f15e9151ab 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -348,6 +348,15 @@ versions: Its parsing caused bugs with the current syntax, so support for the old syntax will be removed in Django 2.0 following an accelerated deprecation. +``Field.rel`` changes +~~~~~~~~~~~~~~~~~~~~~ + +``Field.rel`` and its methods and attributes have changed to match the related +fields API. The ``Field.rel`` attribute is renamed to ``remote_field`` and many +of its methods and attributes are either changed or renamed. + +The aim of these changes is to provide a documented API for relation fields. + Miscellaneous ~~~~~~~~~~~~~ diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index d32411ef674..5083295c467 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -437,7 +437,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase): band.album_set.create( name='Hybrid Theory', cover_art=r'albums\hybrid_theory.jpg' ) - rel = models.Album._meta.get_field('band').rel + rel = models.Album._meta.get_field('band').remote_field w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) self.assertHTMLEqual( @@ -456,7 +456,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase): core = models.Inventory.objects.create( barcode=87, name='Core', parent=apple ) - rel = models.Inventory._meta.get_field('parent').rel + rel = models.Inventory._meta.get_field('parent').remote_field w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) self.assertHTMLEqual( w.render('test', core.parent_id, attrs={}), ( @@ -471,7 +471,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase): # have no magnifying glass link. See #16542 big_honeycomb = models.Honeycomb.objects.create(location='Old tree') big_honeycomb.bee_set.create() - rel = models.Bee._meta.get_field('honeycomb').rel + rel = models.Bee._meta.get_field('honeycomb').remote_field w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) self.assertHTMLEqual( @@ -484,7 +484,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase): # no magnifying glass link. See #16542 subject1 = models.Individual.objects.create(name='Subject #1') models.Individual.objects.create(name='Child', parent=subject1) - rel = models.Individual._meta.get_field('parent').rel + rel = models.Individual._meta.get_field('parent').remote_field w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) self.assertHTMLEqual( @@ -494,7 +494,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase): def test_proper_manager_for_label_lookup(self): # see #9258 - rel = models.Inventory._meta.get_field('parent').rel + rel = models.Inventory._meta.get_field('parent').remote_field w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site) hidden = models.Inventory.objects.create( @@ -521,7 +521,7 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase): m1 = models.Member.objects.create(name='Chester') m2 = models.Member.objects.create(name='Mike') band.members.add(m1, m2) - rel = models.Band._meta.get_field('members').rel + rel = models.Band._meta.get_field('members').remote_field w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site) self.assertHTMLEqual( @@ -546,7 +546,7 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase): c1 = models.Company.objects.create(name='Doodle') c2 = models.Company.objects.create(name='Pear') consultor1.companies.add(c1, c2) - rel = models.Advisor._meta.get_field('companies').rel + rel = models.Advisor._meta.get_field('companies').remote_field w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site) self.assertHTMLEqual( @@ -562,14 +562,14 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase): class RelatedFieldWidgetWrapperTests(DjangoTestCase): def test_no_can_add_related(self): - rel = models.Individual._meta.get_field('parent').rel + rel = models.Individual._meta.get_field('parent').remote_field w = widgets.AdminRadioSelect() # Used to fail with a name error. w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site) self.assertFalse(w.can_add_related) def test_select_multiple_widget_cant_change_delete_related(self): - rel = models.Individual._meta.get_field('parent').rel + rel = models.Individual._meta.get_field('parent').remote_field widget = forms.SelectMultiple() wrapper = widgets.RelatedFieldWidgetWrapper( widget, rel, widget_admin_site, @@ -582,7 +582,7 @@ class RelatedFieldWidgetWrapperTests(DjangoTestCase): self.assertFalse(wrapper.can_delete_related) def test_on_delete_cascade_rel_cant_delete_related(self): - rel = models.Individual._meta.get_field('soulmate').rel + rel = models.Individual._meta.get_field('soulmate').remote_field widget = forms.Select() wrapper = widgets.RelatedFieldWidgetWrapper( widget, rel, widget_admin_site, diff --git a/tests/backends/tests.py b/tests/backends/tests.py index fcd165146e5..737efae411d 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -1058,7 +1058,7 @@ class DBConstraintTestCase(TestCase): self.assertEqual(models.Object.objects.count(), 2) self.assertEqual(obj.related_objects.count(), 1) - intermediary_model = models.Object._meta.get_field("related_objects").rel.through + intermediary_model = models.Object._meta.get_field("related_objects").remote_field.through intermediary_model.objects.create(from_object_id=obj.id, to_object_id=12345) self.assertEqual(obj.related_objects.count(), 1) self.assertEqual(intermediary_model.objects.count(), 2) diff --git a/tests/basic/tests.py b/tests/basic/tests.py index 934c5587f41..d2a96799cf2 100644 --- a/tests/basic/tests.py +++ b/tests/basic/tests.py @@ -766,5 +766,5 @@ class TestRelatedObjectDeprecation(TestCase): self.assertEqual(len(warns), 1) self.assertEqual( str(warns.pop().message), - 'Usage of field.related has been deprecated. Use field.rel instead.' + 'Usage of field.related has been deprecated. Use field.remote_field instead.' ) diff --git a/tests/delete/tests.py b/tests/delete/tests.py index 9465340d0be..5330b4a13b3 100644 --- a/tests/delete/tests.py +++ b/tests/delete/tests.py @@ -153,7 +153,7 @@ class DeletionTests(TestCase): r = R.objects.create() m.m2m.add(r) r.delete() - through = M._meta.get_field('m2m').rel.through + through = M._meta.get_field('m2m').remote_field.through self.assertFalse(through.objects.exists()) r = R.objects.create() diff --git a/tests/field_deconstruction/tests.py b/tests/field_deconstruction/tests.py index bbbbefa7e9f..30c5e892131 100644 --- a/tests/field_deconstruction/tests.py +++ b/tests/field_deconstruction/tests.py @@ -178,8 +178,8 @@ class FieldDeconstructionTests(TestCase): # Test basic pointing from django.contrib.auth.models import Permission field = models.ForeignKey("auth.Permission") - field.rel.to = Permission - field.rel.field_name = "id" + field.remote_field.model = Permission + field.remote_field.field_name = "id" name, path, args, kwargs = field.deconstruct() self.assertEqual(path, "django.db.models.ForeignKey") self.assertEqual(args, []) diff --git a/tests/foreign_object/models.py b/tests/foreign_object/models.py index 1e3214fec7e..177a2007587 100644 --- a/tests/foreign_object/models.py +++ b/tests/foreign_object/models.py @@ -109,7 +109,7 @@ class ArticleTranslationDescriptor(ReverseSingleRelatedObjectDescriptor): if instance is None: raise AttributeError("%s must be accessed via instance" % self.field.name) setattr(instance, self.cache_name, value) - if value is not None and not self.field.rel.multiple: + if value is not None and not self.field.remote_field.multiple: setattr(value, self.field.related.get_cache_name(), instance) diff --git a/tests/many_to_one/tests.py b/tests/many_to_one/tests.py index af7503fba87..d828abe8c26 100644 --- a/tests/many_to_one/tests.py +++ b/tests/many_to_one/tests.py @@ -565,12 +565,12 @@ class ManyToOneTests(TestCase): p = Parent() with self.assertRaisesMessage(ValueError, 'Cannot assign "%r": "%s" instance isn\'t saved in the database.' - % (p, Child.parent.field.rel.to._meta.object_name)): + % (p, Child.parent.field.remote_field.model._meta.object_name)): Child(parent=p) with self.assertRaisesMessage(ValueError, 'Cannot assign "%r": "%s" instance isn\'t saved in the database.' - % (p, Child.parent.field.rel.to._meta.object_name)): + % (p, Child.parent.field.remote_field.model._meta.object_name)): ToFieldChild(parent=p) # Creation using attname keyword argument and an id will cause the @@ -610,7 +610,7 @@ class ManyToOneTests(TestCase): # Regression for #12190 -- Should be able to instantiate a FK outside # of a model, and interrogate its related field. cat = models.ForeignKey(Category) - self.assertEqual('id', cat.rel.get_related_field().name) + self.assertEqual('id', cat.remote_field.get_related_field().name) def test_relation_unsaved(self): # Test that the _set manager does not join on Null value fields (#17541) diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index 2d45dac7f82..59654f95393 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -1068,7 +1068,7 @@ class AutodetectorTests(TestCase): autodetector = MigrationAutodetector(before, after) changes = autodetector._detect_changes() # The field name the FK on the book model points to - self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].rel.field_name, 'id') + self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].remote_field.field_name, 'id') # Now, we test the custom pk field name before = self.make_project_state([]) @@ -1076,7 +1076,7 @@ class AutodetectorTests(TestCase): autodetector = MigrationAutodetector(before, after) changes = autodetector._detect_changes() # The field name the FK on the book model points to - self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].rel.field_name, 'pk_field') + self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].remote_field.field_name, 'pk_field') def test_unmanaged_create(self): """Tests that the autodetector correctly deals with managed models.""" @@ -1126,7 +1126,7 @@ class AutodetectorTests(TestCase): autodetector = MigrationAutodetector(before, after) changes = autodetector._detect_changes() # The field name the FK on the book model points to - self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].rel.field_name, 'id') + self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].remote_field.field_name, 'id') # Now, we test the custom pk field name before = self.make_project_state([]) @@ -1134,7 +1134,7 @@ class AutodetectorTests(TestCase): autodetector = MigrationAutodetector(before, after) changes = autodetector._detect_changes() # The field name the FK on the book model points to - self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].rel.field_name, 'pk_field') + self.assertEqual(changes['otherapp'][0].operations[0].fields[2][1].remote_field.field_name, 'pk_field') @override_settings(AUTH_USER_MODEL="thirdapp.CustomUser") def test_swappable(self): @@ -1159,7 +1159,7 @@ class AutodetectorTests(TestCase): self.assertOperationTypes(changes, 'testapp', 0, ["AlterField"]) self.assertOperationAttributes(changes, 'testapp', 0, 0, model_name="author", name='user') fk_field = changes['testapp'][0].operations[0].field - to_model = '%s.%s' % (fk_field.rel.to._meta.app_label, fk_field.rel.to._meta.object_name) + to_model = '%s.%s' % (fk_field.remote_field.model._meta.app_label, fk_field.remote_field.model._meta.object_name) self.assertEqual(to_model, 'thirdapp.CustomUser') def test_add_field_with_default(self): diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index a3511540589..b5faa8f0f67 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -479,7 +479,7 @@ class OperationTests(OperationTestBase): self.assertNotIn(("test_rnmo", "pony"), new_state.models) self.assertIn(("test_rnmo", "horse"), new_state.models) # RenameModel also repoints all incoming FKs and M2Ms - self.assertEqual("test_rnmo.Horse", new_state.models["test_rnmo", "rider"].fields[1][1].rel.to) + self.assertEqual("test_rnmo.Horse", new_state.models["test_rnmo", "rider"].fields[1][1].remote_field.model) self.assertTableNotExists("test_rnmo_pony") self.assertTableExists("test_rnmo_horse") if connection.features.supports_foreign_keys: @@ -490,7 +490,7 @@ class OperationTests(OperationTestBase): # Test original state and database self.assertIn(("test_rnmo", "pony"), original_state.models) self.assertNotIn(("test_rnmo", "horse"), original_state.models) - self.assertEqual("Pony", original_state.models["test_rnmo", "rider"].fields[1][1].rel.to) + self.assertEqual("Pony", original_state.models["test_rnmo", "rider"].fields[1][1].remote_field.model) self.assertTableExists("test_rnmo_pony") self.assertTableNotExists("test_rnmo_horse") if connection.features.supports_foreign_keys: @@ -515,7 +515,7 @@ class OperationTests(OperationTestBase): self.assertNotIn(("test_rmwsrf", "rider"), new_state.models) self.assertIn(("test_rmwsrf", "horserider"), new_state.models) # Remember, RenameModel also repoints all incoming FKs and M2Ms - self.assertEqual("test_rmwsrf.HorseRider", new_state.models["test_rmwsrf", "horserider"].fields[2][1].rel.to) + self.assertEqual("test_rmwsrf.HorseRider", new_state.models["test_rmwsrf", "horserider"].fields[2][1].remote_field.model) # Test the database alteration self.assertTableExists("test_rmwsrf_rider") self.assertTableNotExists("test_rmwsrf_horserider") @@ -553,8 +553,8 @@ class OperationTests(OperationTestBase): self.assertIn(("test_rmwsc", "littlehorse"), new_state.models) # RenameModel shouldn't repoint the superclass's relations, only local ones self.assertEqual( - project_state.models["test_rmwsc", "rider"].fields[1][1].rel.to, - new_state.models["test_rmwsc", "rider"].fields[1][1].rel.to + project_state.models["test_rmwsc", "rider"].fields[1][1].remote_field.model, + new_state.models["test_rmwsc", "rider"].fields[1][1].remote_field.model ) # Before running the migration we have a table for Shetland Pony, not Little Horse self.assertTableExists("test_rmwsc_shetlandpony") @@ -612,7 +612,7 @@ class OperationTests(OperationTestBase): pony.riders.add(rider) self.assertEqual(Pony.objects.count(), 2) self.assertEqual(Rider.objects.count(), 2) - self.assertEqual(Pony._meta.get_field('riders').rel.through.objects.count(), 2) + self.assertEqual(Pony._meta.get_field('riders').remote_field.through.objects.count(), 2) def test_add_field(self): """ diff --git a/tests/model_fields/test_field_flags.py b/tests/model_fields/test_field_flags.py index e86ee3a5281..47aa0771f1b 100644 --- a/tests/model_fields/test_field_flags.py +++ b/tests/model_fields/test_field_flags.py @@ -155,7 +155,7 @@ class FieldFlagsTests(test.TestCase): # Ensure all m2m reverses are m2m for field in m2m_type_fields: - reverse_field = field.rel + reverse_field = field.remote_field self.assertTrue(reverse_field.is_relation) self.assertTrue(reverse_field.many_to_many) self.assertTrue(reverse_field.related_model) @@ -171,7 +171,7 @@ class FieldFlagsTests(test.TestCase): # Ensure all o2m reverses are m2o for field in o2m_type_fields: if field.concrete: - reverse_field = field.rel + reverse_field = field.remote_field self.assertTrue(reverse_field.is_relation and reverse_field.many_to_one) def test_cardinality_m2o(self): @@ -213,6 +213,6 @@ class FieldFlagsTests(test.TestCase): for field in AllFieldsModel._meta.get_fields(): is_concrete_forward_field = field.concrete and field.related_model if is_concrete_forward_field: - reverse_field = field.rel + reverse_field = field.remote_field self.assertEqual(field.model, reverse_field.related_model) self.assertEqual(field.related_model, reverse_field.model) diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 80d35d18e10..31f2cee3374 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -199,7 +199,7 @@ class ForeignKeyTests(test.TestCase): self.assertEqual(warnings, expected_warnings) def test_related_name_converted_to_text(self): - rel_name = Bar._meta.get_field('a').rel.related_name + rel_name = Bar._meta.get_field('a').remote_field.related_name self.assertIsInstance(rel_name, six.text_type) def test_abstract_model_pending_lookups(self): diff --git a/tests/model_meta/tests.py b/tests/model_meta/tests.py index c77a286308b..340024be47f 100644 --- a/tests/model_meta/tests.py +++ b/tests/model_meta/tests.py @@ -226,7 +226,7 @@ class RelationTreeTests(TestCase): # Testing non hidden related objects self.assertEqual( sorted([field.related_query_name() for field in Relation._meta._relation_tree - if not field.rel.field.rel.is_hidden()]), + if not field.remote_field.field.remote_field.is_hidden()]), sorted([ 'fk_abstract_rel', 'fk_base_rel', 'fk_concrete_rel', 'fo_abstract_rel', 'fo_base_rel', 'fo_concrete_rel', 'm2m_abstract_rel', diff --git a/tests/model_options/models/tablespaces.py b/tests/model_options/models/tablespaces.py index 7328a3df6ee..42fe1356c8e 100644 --- a/tests/model_options/models/tablespaces.py +++ b/tests/model_options/models/tablespaces.py @@ -42,8 +42,8 @@ class Article(models.Model): # Also set the tables for automatically created models -Authors = Article._meta.get_field('authors').rel.through +Authors = Article._meta.get_field('authors').remote_field.through Authors._meta.db_table = 'model_options_articleref_authors' -Reviewers = Article._meta.get_field('reviewers').rel.through +Reviewers = Article._meta.get_field('reviewers').remote_field.through Reviewers._meta.db_table = 'model_options_articleref_reviewers' diff --git a/tests/one_to_one/tests.py b/tests/one_to_one/tests.py index 2cda25b0aa6..4047e2d1fdc 100644 --- a/tests/one_to_one/tests.py +++ b/tests/one_to_one/tests.py @@ -136,7 +136,7 @@ class OneToOneTests(TestCase): place = Place(name='User', address='London') with self.assertRaisesMessage(ValueError, 'Cannot assign "%r": "%s" instance isn\'t saved in the database.' - % (place, Restaurant.place.field.rel.to._meta.object_name)): + % (place, Restaurant.place.field.remote_field.model._meta.object_name)): Restaurant.objects.create(place=place, serves_hot_dogs=True, serves_pizza=False) bar = UndergroundBar() p = Place(name='User', address='London') @@ -410,7 +410,7 @@ class OneToOneTests(TestCase): be added to the related model. """ self.assertFalse( - hasattr(Target, HiddenPointer._meta.get_field('target').rel.get_accessor_name()) + hasattr(Target, HiddenPointer._meta.get_field('target').remote_field.get_accessor_name()) ) def test_related_object(self): diff --git a/tests/queryset_pickle/tests.py b/tests/queryset_pickle/tests.py index e8420008541..d31b0cce1a5 100644 --- a/tests/queryset_pickle/tests.py +++ b/tests/queryset_pickle/tests.py @@ -82,7 +82,7 @@ class PickleabilityTestCase(TestCase): m1 = M2MModel.objects.create() g1 = Group.objects.create(name='foof') m1.groups.add(g1) - m2m_through = M2MModel._meta.get_field('groups').rel.through + m2m_through = M2MModel._meta.get_field('groups').remote_field.through original = m2m_through.objects.get() dumped = pickle.dumps(original) reloaded = pickle.loads(dumped) diff --git a/tests/schema/fields.py b/tests/schema/fields.py index 1436b971b7e..0392a29aa26 100644 --- a/tests/schema/fields.py +++ b/tests/schema/fields.py @@ -35,12 +35,13 @@ class CustomManyToManyField(RelatedField): super(CustomManyToManyField, self).__init__(**kwargs) def contribute_to_class(self, cls, name, **kwargs): - if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name): - self.rel.related_name = "%s_rel_+" % name + if self.remote_field.symmetrical and ( + self.remote_field.model == "self" or self.remote_field.model == cls._meta.object_name): + self.remote_field.related_name = "%s_rel_+" % name super(CustomManyToManyField, self).contribute_to_class(cls, name, **kwargs) - if not self.rel.through and not cls._meta.abstract and not cls._meta.swapped: - self.rel.through = create_many_to_many_intermediary_model(self, cls) - setattr(cls, self.name, ManyRelatedObjectsDescriptor(self.rel)) + if not self.remote_field.through and not cls._meta.abstract and not cls._meta.swapped: + self.remote_field.through = create_many_to_many_intermediary_model(self, cls) + setattr(cls, self.name, ManyRelatedObjectsDescriptor(self.remote_field)) self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) def get_internal_type(self): diff --git a/tests/schema/tests.py b/tests/schema/tests.py index df45ed2d595..f6b80b9c835 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -68,7 +68,7 @@ class SchemaTests(TransactionTestCase): # Remove any M2M tables first for field in model._meta.local_many_to_many: with atomic(): - tbl = field.rel.through._meta.db_table + tbl = field.remote_field.through._meta.db_table if tbl in table_names: cursor.execute(connection.schema_editor().sql_delete_table % { "table": connection.ops.quote_name(tbl), @@ -244,7 +244,7 @@ class SchemaTests(TransactionTestCase): with connection.schema_editor() as editor: editor.add_field(LocalAuthorWithM2M, new_field) # Make sure no FK constraint is present - constraints = self.get_constraints(new_field.rel.through._meta.db_table) + constraints = self.get_constraints(new_field.remote_field.through._meta.db_table) for name, details in constraints.items(): if details['columns'] == ["tag_id"] and details['foreign_key']: self.fail("FK constraint for tag_id found") @@ -740,7 +740,7 @@ class SchemaTests(TransactionTestCase): editor.create_model(TagM2MTest) editor.create_model(LocalBookWithM2M) # Ensure there is now an m2m table there - columns = self.column_classes(LocalBookWithM2M._meta.get_field("tags").rel.through) + columns = self.column_classes(LocalBookWithM2M._meta.get_field("tags").remote_field.through) self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField") def test_m2m_create(self): @@ -813,12 +813,12 @@ class SchemaTests(TransactionTestCase): new_field = M2MFieldClass("schema.TagM2MTest", related_name="authors") new_field.contribute_to_class(LocalAuthorWithM2M, "tags") # Ensure there's no m2m table there - self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) + self.assertRaises(DatabaseError, self.column_classes, new_field.remote_field.through) # Add the field with connection.schema_editor() as editor: editor.add_field(LocalAuthorWithM2M, new_field) # Ensure there is now an m2m table there - columns = self.column_classes(new_field.rel.through) + columns = self.column_classes(new_field.remote_field.through) self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField") # "Alter" the field. This should not rename the DB table to itself. @@ -829,7 +829,7 @@ class SchemaTests(TransactionTestCase): with connection.schema_editor() as editor: editor.remove_field(LocalAuthorWithM2M, new_field) # Ensure there's no m2m table there - self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through) + self.assertRaises(DatabaseError, self.column_classes, new_field.remote_field.through) def test_m2m(self): self._test_m2m(ManyToManyField) @@ -910,7 +910,7 @@ class SchemaTests(TransactionTestCase): editor.create_model(TagM2MTest) editor.create_model(UniqueTest) # Ensure the M2M exists and points to TagM2MTest - constraints = self.get_constraints(LocalBookWithM2M._meta.get_field("tags").rel.through._meta.db_table) + constraints = self.get_constraints(LocalBookWithM2M._meta.get_field("tags").remote_field.through._meta.db_table) if connection.features.supports_foreign_keys: for name, details in constraints.items(): if details['columns'] == ["tagm2mtest_id"] and details['foreign_key']: @@ -925,9 +925,9 @@ class SchemaTests(TransactionTestCase): with connection.schema_editor() as editor: editor.alter_field(LocalBookWithM2M, old_field, new_field) # Ensure old M2M is gone - self.assertRaises(DatabaseError, self.column_classes, LocalBookWithM2M._meta.get_field("tags").rel.through) + self.assertRaises(DatabaseError, self.column_classes, LocalBookWithM2M._meta.get_field("tags").remote_field.through) # Ensure the new M2M exists and points to UniqueTest - constraints = self.get_constraints(new_field.rel.through._meta.db_table) + constraints = self.get_constraints(new_field.remote_field.through._meta.db_table) if connection.features.supports_foreign_keys: for name, details in constraints.items(): if details['columns'] == ["uniquetest_id"] and details['foreign_key']: