rel objects for related fields rel objects for lack of a better name carry information about the relation modeled by a related field and provide some utility functions they re stored in the remotefield attribute of the field they also act as reverse fields for the purposes of the meta api because they re the closest concept currently available used by foreignobject to store information about the relation meta getfields returns this class to provide access to the field flags for the reverse relation field flags reverse relations are always nullable django can t enforce that a foreign key on the related model points to this model some of the following cachedproperties can t be initialized in init as the field doesn t have its model yet calling these methods before field contributetoclass has been called will result in attributeerror when filtering against this relation return the field on the remote model against which the filtering should happen delete the pathinfos cached property because it can be recalculated at first invocation after deserialization the attribute must be removed because subclasses like manytoonerel may have a pathinfo which contains an intermediate m2m table that s been dynamically created and doesn t exist in the models module this is a reverse relation so there is no reversepathinfos to delete return choices with a default blank choices included for use as select choices for this field analog of django db models fields field getchoices provided initially for utilization by relatedfieldlistfilter should the related object be hidden return boolself relatedname and self relatedname1 def getjoiningcolumnsself warnings warn foreignobjectrel getjoiningcolumns is deprecated use getjoiningfields instead removedindjango60warning return self field getreversejoiningcolumns def getjoiningfieldsself return self field getreversejoiningfields def getextrarestrictionself alias relatedalias return self field getextrarestrictionrelatedalias alias def setfieldnameself by default foreign object doesn t relate to any remote field for example custom multicolumn joins currently have no remote field self fieldname none def getaccessornameself modelnone this method encapsulates the logic that decides what name to give an accessor descriptor that retrieves related manytoone or manytomany objects it uses the lowercased objectname set but this can be overridden with the relatedname option due to backwards compatibility modelforms need to be able to provide an alternate model see baseinlineformset getdefaultprefix opts model meta if model else self relatedmodel meta model model or self relatedmodel if self multiple if this is a symmetrical m2m relation on self there is no reverse accessor if self symmetrical and model self model return none if self relatedname return self relatedname return opts modelname set if self multiple else def getpathinfoself filteredrelationnone if filteredrelation return self field getreversepathinfofilteredrelation else return self field reversepathinfos cachedproperty def pathinfosself return self getpathinfo def getcachenameself return self getaccessorname class manytoonerelforeignobjectrel def init self field to fieldname relatednamenone relatedquerynamenone limitchoicestonone parentlinkfalse ondeletenone super init field to relatednamerelatedname relatedquerynamerelatedqueryname limitchoicestolimitchoicesto parentlinkparentlink ondeleteondelete self fieldname fieldname def getstateself state super getstate state poprelatedmodel none return state property def identityself return super identity self fieldname def getrelatedfieldself field self model meta getfieldself fieldname if not field concrete raise exceptions fielddoesnotexist no related field named s self fieldname return field def setfieldnameself self fieldname self fieldname or self model meta pk name class onetoonerelmanytoonerel def init self field to fieldname relatednamenone relatedquerynamenone limitchoicestonone parentlinkfalse ondeletenone super init field to fieldname relatednamerelatedname relatedquerynamerelatedqueryname limitchoicestolimitchoicesto parentlinkparentlink ondeleteondelete self multiple false class manytomanyrelforeignobjectrel def init self field to relatednamenone relatedquerynamenone limitchoicestonone symmetricaltrue throughnone throughfieldsnone dbconstrainttrue super init field to relatednamerelatedname relatedquerynamerelatedqueryname limitchoicestolimitchoicesto if through and not dbconstraint raise valueerrorcan t supply a through model and dbconstraintfalse self through through if throughfields and not through raise valueerrorcannot specify throughfields without a through model self throughfields throughfields self symmetrical symmetrical self dbconstraint dbconstraint property def identityself return super identity self through makehashableself throughfields self dbconstraint def getrelatedfieldself opts self through meta if self throughfields field opts getfieldself throughfields0 else for field in opts fields rel getattrfield remotefield none if rel and rel model self model break return field foreignrelatedfields0 used by foreignobject to store information about the relation _meta get_fields returns this class to provide access to the field flags for the reverse relation field flags reverse relations are always nullable django can t enforce that a foreign key on the related model points to this model some of the following cached_properties can t be initialized in __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 when filtering against this relation return the field on the remote model against which the filtering should happen delete the path_infos cached property because it can be recalculated at first invocation after deserialization the attribute must be removed because subclasses like manytoonerel may have a pathinfo which contains an intermediate m2m table that s been dynamically created and doesn t exist in the models module this is a reverse relation so there is no reverse_path_infos to delete return choices with a default blank choices included for use as select choices for this field analog of django db models fields field get_choices provided initially for utilization by relatedfieldlistfilter should the related object be hidden set the related field s name this is not available until later stages of app loading so set_field_name is called from set_attributes_from_rel by default foreign object doesn t relate to any remote field for example custom multicolumn joins currently have no remote field this method encapsulates the logic that decides what name to give an accessor descriptor that retrieves related many to one or many to many objects it uses the lowercased object_name _set but this can be overridden with the related_name option due to backwards compatibility modelforms need to be able to provide an alternate model see baseinlineformset get_default_prefix if this is a symmetrical m2m relation on self there is no reverse accessor return the name of the cache key to use for storing an instance of the forward model on the reverse model used by the foreignkey field to store information about the relation _meta get_fields returns this class to provide access to the field flags for the reverse relation note because we somewhat abuse the rel objects by using them as reverse fields we get the funny situation where manytoonerel many_to_one false and manytoonerel one_to_many true this is unfortunate but the actual manytoonerel class is a private api and there is work underway to turn reverse relations into actual fields return the field in the to object to which this relationship is tied used by onetoonefield to store information about the relation _meta get_fields returns this class to provide access to the field flags for the reverse relation used by manytomanyfield to store information about the relation _meta get_fields returns this class to provide access to the field flags for the reverse relation return the field in the to object to which this relationship is tied provided for symmetry with manytoonerel
import warnings

from django.core import exceptions
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.functional import cached_property
from django.utils.hashable import make_hashable

from . import BLANK_CHOICE_DASH
from .mixins import FieldCacheMixin


class ForeignObjectRel(FieldCacheMixin):
    

    
    auto_created = True
    concrete = False
    editable = False
    is_relation = True

    
    
    null = True
    empty_strings_allowed = False

    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.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
        self.parent_link = parent_link
        self.on_delete = on_delete

        self.symmetrical = False
        self.multiple = True

    
    
    
    
    @cached_property
    def hidden(self):
        return self.is_hidden()

    @cached_property
    def name(self):
        return self.field.related_query_name()

    @property
    def remote_field(self):
        return self.field

    @property
    def target_field(self):
        
        target_fields = self.path_infos[-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:
            raise AttributeError(
                "This property can't be accessed before self.field.contribute_to_class "
                "has been called."
            )
        return self.field.model

    @cached_property
    def many_to_many(self):
        return self.field.many_to_many

    @cached_property
    def many_to_one(self):
        return self.field.one_to_many

    @cached_property
    def one_to_many(self):
        return self.field.many_to_one

    @cached_property
    def one_to_one(self):
        return self.field.one_to_one

    def get_lookup(self, lookup_name):
        return self.field.get_lookup(lookup_name)

    def get_lookups(self):
        return self.field.get_lookups()

    def get_transform(self, name):
        return self.field.get_transform(name)

    def get_internal_type(self):
        return self.field.get_internal_type()

    @property
    def db_type(self):
        return self.field.db_type

    def __repr__(self):
        return "<%s: %s.%s>" % (
            type(self).__name__,
            self.related_model._meta.app_label,
            self.related_model._meta.model_name,
        )

    @property
    def identity(self):
        return (
            self.field,
            self.model,
            self.related_name,
            self.related_query_name,
            make_hashable(self.limit_choices_to),
            self.parent_link,
            self.on_delete,
            self.symmetrical,
            self.multiple,
        )

    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.identity == other.identity

    def __hash__(self):
        return hash(self.identity)

    def __getstate__(self):
        state = self.__dict__.copy()
        
        
        
        
        
        
        
        state.pop("path_infos", None)
        return state

    def get_choices(
        self,
        include_blank=True,
        blank_choice=BLANK_CHOICE_DASH,
        limit_choices_to=None,
        ordering=(),
    ):
        
        limit_choices_to = limit_choices_to or self.limit_choices_to
        qs = self.related_model._default_manager.complex_filter(limit_choices_to)
        if ordering:
            qs = qs.order_by(*ordering)
        return (blank_choice if include_blank else []) + [(x.pk, str(x)) for x in qs]

    def is_hidden(self):
        
        return bool(self.related_name) and self.related_name[-1] == "+"

    def get_joining_columns(self):
        warnings.warn(
            "ForeignObjectRel.get_joining_columns() is deprecated. Use "
            "get_joining_fields() instead.",
            RemovedInDjango60Warning,
        )
        return self.field.get_reverse_joining_columns()

    def get_joining_fields(self):
        return self.field.get_reverse_joining_fields()

    def get_extra_restriction(self, alias, related_alias):
        return self.field.get_extra_restriction(related_alias, alias)

    def set_field_name(self):
        
        
        
        self.field_name = None

    def get_accessor_name(self, model=None):
        
        
        
        
        
        
        opts = model._meta if model else self.related_model._meta
        model = model or self.related_model
        if self.multiple:
            
            
            if self.symmetrical and model == self.model:
                return None
        if self.related_name:
            return self.related_name
        return opts.model_name + ("_set" if self.multiple else "")

    def get_path_info(self, filtered_relation=None):
        if filtered_relation:
            return self.field.get_reverse_path_info(filtered_relation)
        else:
            return self.field.reverse_path_infos

    @cached_property
    def path_infos(self):
        return self.get_path_info()

    def get_cache_name(self):
        
        return self.get_accessor_name()


class ManyToOneRel(ForeignObjectRel):
    

    def __init__(
        self,
        field,
        to,
        field_name,
        related_name=None,
        related_query_name=None,
        limit_choices_to=None,
        parent_link=False,
        on_delete=None,
    ):
        super().__init__(
            field,
            to,
            related_name=related_name,
            related_query_name=related_query_name,
            limit_choices_to=limit_choices_to,
            parent_link=parent_link,
            on_delete=on_delete,
        )

        self.field_name = field_name

    def __getstate__(self):
        state = super().__getstate__()
        state.pop("related_model", None)
        return state

    @property
    def identity(self):
        return super().identity + (self.field_name,)

    def get_related_field(self):
        
        field = self.model._meta.get_field(self.field_name)
        if not field.concrete:
            raise exceptions.FieldDoesNotExist(
                "No related field named '%s'" % self.field_name
            )
        return field

    def set_field_name(self):
        self.field_name = self.field_name or self.model._meta.pk.name


class OneToOneRel(ManyToOneRel):
    

    def __init__(
        self,
        field,
        to,
        field_name,
        related_name=None,
        related_query_name=None,
        limit_choices_to=None,
        parent_link=False,
        on_delete=None,
    ):
        super().__init__(
            field,
            to,
            field_name,
            related_name=related_name,
            related_query_name=related_query_name,
            limit_choices_to=limit_choices_to,
            parent_link=parent_link,
            on_delete=on_delete,
        )

        self.multiple = False


class ManyToManyRel(ForeignObjectRel):
    

    def __init__(
        self,
        field,
        to,
        related_name=None,
        related_query_name=None,
        limit_choices_to=None,
        symmetrical=True,
        through=None,
        through_fields=None,
        db_constraint=True,
    ):
        super().__init__(
            field,
            to,
            related_name=related_name,
            related_query_name=related_query_name,
            limit_choices_to=limit_choices_to,
        )

        if through and not db_constraint:
            raise ValueError("Can't supply a through model and db_constraint=False")
        self.through = through

        if through_fields and not through:
            raise ValueError("Cannot specify through_fields without a through model")
        self.through_fields = through_fields

        self.symmetrical = symmetrical
        self.db_constraint = db_constraint

    @property
    def identity(self):
        return super().identity + (
            self.through,
            make_hashable(self.through_fields),
            self.db_constraint,
        )

    def get_related_field(self):
        
        opts = self.through._meta
        if self.through_fields:
            field = opts.get_field(self.through_fields[0])
        else:
            for field in opts.fields:
                rel = getattr(field, "remote_field", None)
                if rel and rel.model == self.model:
                    break
        return field.foreign_related_fields[0]