|
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642 |
- from __future__ import unicode_literals
-
- import warnings
- from functools import partial
-
- from django import forms
- from django.apps import apps
- from django.core import checks, exceptions
- from django.db import connection, router
- from django.db.backends import utils
- from django.db.models import Q
- from django.db.models.deletion import CASCADE, SET_DEFAULT, SET_NULL
- from django.db.models.query_utils import PathInfo
- from django.db.models.utils import make_model_tuple
- from django.utils import six
- from django.utils.deprecation import (
- RemovedInDjango20Warning, RemovedInDjango110Warning,
- )
- 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 _
- from django.utils.version import get_docs_version
-
- from . import (
- AutoField, Field, IntegerField, PositiveIntegerField,
- PositiveSmallIntegerField,
- )
- from .related_descriptors import (
- ForwardManyToOneDescriptor, ManyToManyDescriptor,
- ReverseManyToOneDescriptor, ReverseOneToOneDescriptor,
- )
- from .related_lookups import (
- RelatedExact, RelatedGreaterThan, RelatedGreaterThanOrEqual, RelatedIn,
- RelatedIsNull, RelatedLessThan, RelatedLessThanOrEqual,
- )
- from .reverse_related import (
- ForeignObjectRel, ManyToManyRel, ManyToOneRel, OneToOneRel,
- )
-
- RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
-
-
- def resolve_relation(scope_model, relation):
- """
- Transform relation into a model or fully-qualified model string of the form
- "app_label.ModelName", relative to scope_model.
-
- The relation argument can be:
- * RECURSIVE_RELATIONSHIP_CONSTANT, i.e. the string "self", in which case
- the model argument will be returned.
- * A bare model name without an app_label, in which case scope_model's
- app_label will be prepended.
- * An "app_label.ModelName" string.
- * A model class, which will be returned unchanged.
- """
- # Check for recursive relations
- if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
- relation = scope_model
-
- # Look for an "app.Model" relation
- if isinstance(relation, six.string_types):
- if "." not in relation:
- relation = "%s.%s" % (scope_model._meta.app_label, relation)
-
- return relation
-
-
- def lazy_related_operation(function, model, *related_models, **kwargs):
- """
- Schedule `function` to be called once `model` and all `related_models`
- have been imported and registered with the app registry. `function` will
- be called with the newly-loaded model classes as its positional arguments,
- plus any optional keyword arguments.
-
- The `model` argument must be a model class. Each subsequent positional
- argument is another model, or a reference to another model - see
- `resolve_relation()` for the various forms these may take. Any relative
- references will be resolved relative to `model`.
-
- This is a convenience wrapper for `Apps.lazy_model_operation` - the app
- registry model used is the one found in `model._meta.apps`.
- """
- models = [model] + [resolve_relation(model, rel) for rel in related_models]
- model_keys = (make_model_tuple(m) for m in models)
- apps = model._meta.apps
- return apps.lazy_model_operation(partial(function, **kwargs), *model_keys)
-
-
- def add_lazy_relation(cls, field, relation, operation):
- warnings.warn(
- "add_lazy_relation() has been superseded by lazy_related_operation() "
- "and related methods on the Apps class.",
- RemovedInDjango20Warning, stacklevel=2)
- # Rearrange args for new Apps.lazy_model_operation
- function = lambda local, related, field: operation(field, related, local)
- lazy_related_operation(function, cls, relation, field=field)
-
-
- class RelatedField(Field):
- """
- Base class that all relational fields inherit from.
- """
-
- # Field flags
- one_to_many = False
- one_to_one = False
- many_to_many = False
- many_to_one = False
-
- @cached_property
- def related_model(self):
- # Can't cache this property until all the models are loaded.
- apps.check_models_ready()
- return self.remote_field.model
-
- def check(self, **kwargs):
- errors = super(RelatedField, self).check(**kwargs)
- errors.extend(self._check_related_name_is_valid())
- errors.extend(self._check_relation_model_exists())
- errors.extend(self._check_referencing_to_swapped_model())
- errors.extend(self._check_clashes())
- return errors
-
- def _check_related_name_is_valid(self):
- import re
- import keyword
- related_name = self.remote_field.related_name
- if related_name is None:
- return []
- is_valid_id = True
- if keyword.iskeyword(related_name):
- is_valid_id = False
- if six.PY3:
- if not related_name.isidentifier():
- is_valid_id = False
- else:
- if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\Z', related_name):
- is_valid_id = False
- if not (is_valid_id or related_name.endswith('+')):
- return [
- checks.Error(
- "The name '%s' is invalid related_name for field %s.%s" %
- (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,
- id='fields.E306',
- )
- ]
- return []
-
- def _check_relation_model_exists(self):
- rel_is_missing = self.remote_field.model not in self.opts.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 "
- "is either not installed, or is abstract.") % model_name,
- hint=None,
- obj=self,
- id='fields.E300',
- )
- ]
- return []
-
- def _check_referencing_to_swapped_model(self):
- if (self.remote_field.model not in self.opts.apps.get_models() and
- not isinstance(self.remote_field.model, six.string_types) and
- self.remote_field.model._meta.swapped):
- model = "%s.%s" % (
- 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.remote_field.model._meta.swappable,
- obj=self,
- id='fields.E301',
- )
- ]
- return []
-
- def _check_clashes(self):
- """
- Check accessor and reverse query name clashes.
- """
- from django.db.models.base import ModelBase
-
- errors = []
- opts = self.model._meta
-
- # `f.remote_field.model` may be a string instead of a model. Skip if model name is
- # not resolved.
- if not isinstance(self.remote_field.model, ModelBase):
- return []
-
- # Consider that we are checking field `Model.foreign` and the models
- # are:
- #
- # class Target(models.Model):
- # model = models.IntegerField()
- # model_set = models.IntegerField()
- #
- # class Model(models.Model):
- # foreign = models.ForeignKey(Target)
- # m2m = models.ManyToManyField(Target)
-
- # rel_opts.object_name == "Target"
- rel_opts = self.remote_field.model._meta
- # If the field doesn't install a backward relation on the target model
- # (so `is_hidden` returns True), then there are no clashes to check
- # and we can skip these fields.
- rel_is_hidden = self.remote_field.is_hidden()
- 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"
-
- # Check clashes between accessor or reverse query name of `field`
- # and any other field name -- i.e. accessor for Model.foreign is
- # model_set and it clashes with Target.model_set.
- potential_clashes = rel_opts.fields + rel_opts.many_to_many
- for clash_field in potential_clashes:
- clash_name = "%s.%s" % (rel_opts.object_name,
- clash_field.name) # i. e. "Target.model_set"
- if not rel_is_hidden and clash_field.name == rel_name:
- errors.append(
- checks.Error(
- "Reverse accessor for '%s' clashes with field name '%s'." % (field_name, clash_name),
- hint=("Rename field '%s', or add/change a related_name "
- "argument to the definition for field '%s'.") % (clash_name, field_name),
- obj=self,
- id='fields.E302',
- )
- )
-
- if clash_field.name == rel_query_name:
- errors.append(
- checks.Error(
- "Reverse query name for '%s' clashes with field name '%s'." % (field_name, clash_name),
- hint=("Rename field '%s', or add/change a related_name "
- "argument to the definition for field '%s'.") % (clash_name, field_name),
- obj=self,
- id='fields.E303',
- )
- )
-
- # Check clashes between accessors/reverse query names of `field` and
- # any other field accessor -- i. e. Model.foreign accessor clashes with
- # Model.m2m accessor.
- potential_clashes = (r for r in rel_opts.related_objects if r.field is not self)
- for clash_field in potential_clashes:
- clash_name = "%s.%s" % ( # i. e. "Model.m2m"
- clash_field.related_model._meta.object_name,
- clash_field.field.name)
- if not rel_is_hidden and clash_field.get_accessor_name() == rel_name:
- errors.append(
- checks.Error(
- "Reverse accessor for '%s' clashes with reverse accessor for '%s'." % (field_name, clash_name),
- hint=("Add or change a related_name argument "
- "to the definition for '%s' or '%s'.") % (field_name, clash_name),
- obj=self,
- id='fields.E304',
- )
- )
-
- if clash_field.get_accessor_name() == rel_query_name:
- errors.append(
- checks.Error(
- "Reverse query name for '%s' clashes with reverse query name for '%s'."
- % (field_name, clash_name),
- hint=("Add or change a related_name argument "
- "to the definition for '%s' or '%s'.") % (field_name, clash_name),
- obj=self,
- id='fields.E305',
- )
- )
-
- return errors
-
- def db_type(self, connection):
- # By default related field will not have a column as it relates to
- # columns from another table.
- return None
-
- def contribute_to_class(self, cls, name, virtual_only=False):
-
- super(RelatedField, self).contribute_to_class(cls, name, virtual_only=virtual_only)
-
- self.opts = cls._meta
-
- if not cls._meta.abstract:
- 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.remote_field.related_name = related_name
-
- def resolve_related_class(model, related, field):
- field.remote_field.model = related
- field.do_related_class(related, model)
- lazy_related_operation(resolve_related_class, cls, self.remote_field.model, field=self)
-
- def get_forward_related_filter(self, obj):
- """
- Return the keyword arguments that when supplied to
- self.model.object.filter(), would select all instances related through
- this field to the remote obj. This is used to build the querysets
- returned by related descriptors. obj is an instance of
- self.related_field.model.
- """
- return {
- '%s__%s' % (self.name, rh_field.name): getattr(obj, rh_field.attname)
- for _, rh_field in self.related_fields
- }
-
- def get_reverse_related_filter(self, obj):
- """
- Complement to get_forward_related_filter(). Return the keyword
- arguments that when passed to self.related_field.model.object.filter()
- select all instances of self.related_field.model related through
- this field to obj. obj is an instance of self.model.
- """
- base_filter = {
- rh_field.attname: getattr(obj, lh_field.attname)
- for lh_field, rh_field in self.related_fields
- }
- descriptor_filter = self.get_extra_descriptor_filter(obj)
- base_q = Q(**base_filter)
- if isinstance(descriptor_filter, dict):
- return base_q & Q(**descriptor_filter)
- elif descriptor_filter:
- return base_q & descriptor_filter
- return base_q
-
- @property
- def swappable_setting(self):
- """
- Get the setting that this is powered from for swapping, or None
- if it's not swapped in / marked with swappable=False.
- """
- if self.swappable:
- # Work out string form of "to"
- if isinstance(self.remote_field.model, six.string_types):
- to_string = self.remote_field.model
- else:
- to_string = self.remote_field.model._meta.label
- return apps.get_swappable_settings_name(to_string)
- return None
-
- def set_attributes_from_rel(self):
- 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.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.remote_field instead.",
- RemovedInDjango110Warning, 2)
- return self.remote_field
-
- def do_related_class(self, other, cls):
- self.set_attributes_from_rel()
- self.contribute_to_related_class(other, self.remote_field)
-
- def get_limit_choices_to(self):
- """
- Return ``limit_choices_to`` for this model field.
-
- If it is a callable, it will be invoked and the result will be
- returned.
- """
- 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):
- """
- Pass ``limit_choices_to`` to the field being constructed.
-
- Only passes it if there is a type that supports related fields.
- This is a similar strategy used to pass the ``queryset`` to the field
- being constructed.
- """
- defaults = {}
- 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.remote_field.limit_choices_to
- defaults.update({
- 'limit_choices_to': limit_choices_to,
- })
- defaults.update(kwargs)
- return super(RelatedField, self).formfield(**defaults)
-
- def related_query_name(self):
- """
- Define the name that can be used to identify this related object in a
- table-spanning query.
- """
- 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 ForeignObject(RelatedField):
- """
- Abstraction of the ForeignKey relation, supports multi-column relations.
- """
-
- # Field flags
- many_to_many = False
- many_to_one = True
- one_to_many = False
- one_to_one = False
-
- requires_unique_target = True
- related_accessor_class = ReverseManyToOneDescriptor
- rel_class = ForeignObjectRel
-
- def __init__(self, to, on_delete, from_fields, to_fields, rel=None, related_name=None,
- related_query_name=None, limit_choices_to=None, parent_link=False,
- swappable=True, **kwargs):
-
- if rel is None:
- rel = self.rel_class(
- self, 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,
- )
-
- super(ForeignObject, self).__init__(rel=rel, **kwargs)
-
- self.from_fields = from_fields
- self.to_fields = to_fields
- self.swappable = swappable
-
- def check(self, **kwargs):
- errors = super(ForeignObject, self).check(**kwargs)
- errors.extend(self._check_unique_target())
- return errors
-
- def _check_unique_target(self):
- rel_is_string = isinstance(self.remote_field.model, six.string_types)
- if rel_is_string or not self.requires_unique_target:
- return []
-
- try:
- self.foreign_related_fields
- except exceptions.FieldDoesNotExist:
- return []
-
- if not self.foreign_related_fields:
- return []
-
- unique_foreign_fields = {
- frozenset([f.name])
- for f in self.remote_field.model._meta.get_fields()
- if getattr(f, 'unique', False)
- }
- unique_foreign_fields.update({
- frozenset(ut)
- for ut in self.remote_field.model._meta.unique_together
- })
- foreign_fields = {f.name for f in self.foreign_related_fields}
- has_unique_constraint = any(u <= foreign_fields for u in unique_foreign_fields)
-
- if not has_unique_constraint 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.remote_field.model.__name__
- return [
- checks.Error(
- "No subset of the fields %s on model '%s' is unique."
- % (field_combination, model_name),
- hint=(
- "Add unique=True on any of those fields or add at "
- "least a subset of them to a unique_together constraint."
- ),
- obj=self,
- id='fields.E310',
- )
- ]
- elif not has_unique_constraint:
- field_name = self.foreign_related_fields[0].name
- model_name = self.remote_field.model.__name__
- return [
- checks.Error(
- ("'%s.%s' must set unique=True "
- "because it is referenced by a foreign key.") % (model_name, field_name),
- hint=None,
- obj=self,
- id='fields.E311',
- )
- ]
- else:
- return []
-
- def deconstruct(self):
- name, path, args, kwargs = super(ForeignObject, self).deconstruct()
- kwargs['on_delete'] = self.remote_field.on_delete
- kwargs['from_fields'] = self.from_fields
- kwargs['to_fields'] = self.to_fields
-
- 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.parent_link:
- kwargs['parent_link'] = self.remote_field.parent_link
- # Work out string form of "to"
- if isinstance(self.remote_field.model, six.string_types):
- kwargs['to'] = self.remote_field.model
- else:
- 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
- if swappable_setting is not None:
- # If it's already a settings reference, error
- if hasattr(kwargs['to'], "setting_name"):
- if kwargs['to'].setting_name != swappable_setting:
- raise ValueError(
- "Cannot deconstruct a ForeignKey pointing to a model "
- "that is swapped in place of more than one model (%s and %s)"
- % (kwargs['to'].setting_name, swappable_setting)
- )
- # Set it
- from django.db.migrations.writer import SettingsReference
- kwargs['to'] = SettingsReference(
- kwargs['to'],
- swappable_setting,
- )
- return name, path, args, kwargs
-
- 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.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.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
-
- @property
- def related_fields(self):
- if not hasattr(self, '_related_fields'):
- self._related_fields = self.resolve_related_fields()
- return self._related_fields
-
- @property
- def reverse_related_fields(self):
- return [(rhs_field, lhs_field) for lhs_field, rhs_field in self.related_fields]
-
- @property
- def local_related_fields(self):
- return tuple(lhs_field for lhs_field, rhs_field in self.related_fields)
-
- @property
- def foreign_related_fields(self):
- return tuple(rhs_field for lhs_field, rhs_field in self.related_fields if rhs_field)
-
- def get_local_related_value(self, instance):
- return self.get_instance_value_for_fields(instance, self.local_related_fields)
-
- def get_foreign_related_value(self, instance):
- return self.get_instance_value_for_fields(instance, self.foreign_related_fields)
-
- @staticmethod
- def get_instance_value_for_fields(instance, fields):
- ret = []
- opts = instance._meta
- for field in fields:
- # Gotcha: in some cases (like fixture loading) a model can have
- # different values in parent_ptr_id and parent's id. So, use
- # instance.pk (that is, parent_ptr_id) when asked for instance.id.
- if field.primary_key:
- possible_parent_link = opts.get_ancestor_link(field.model)
- if (not possible_parent_link or
- possible_parent_link.primary_key or
- possible_parent_link.model._meta.abstract):
- ret.append(instance.pk)
- continue
- ret.append(getattr(instance, field.attname))
- return tuple(ret)
-
- def get_attname_column(self):
- attname, column = super(ForeignObject, self).get_attname_column()
- return attname, None
-
- def get_joining_columns(self, reverse_join=False):
- source = self.reverse_related_fields if reverse_join else self.related_fields
- return tuple((lhs_field.column, rhs_field.column) for lhs_field, rhs_field in source)
-
- def get_reverse_joining_columns(self):
- return self.get_joining_columns(reverse_join=True)
-
- def get_extra_descriptor_filter(self, instance):
- """
- Return an extra filter condition for related object fetching when
- user does 'instance.fieldname', that is the extra filter is used in
- the descriptor of the field.
-
- The filter should be either a dict usable in .filter(**kwargs) call or
- a Q-object. The condition will be ANDed together with the relation's
- joining columns.
-
- A parallel method is get_extra_restriction() which is used in
- JOIN and subquery conditions.
- """
- return {}
-
- def get_extra_restriction(self, where_class, alias, related_alias):
- """
- Return a pair condition used for joining and subquery pushdown. The
- condition is something that responds to as_sql(compiler, connection)
- method.
-
- Note that currently referring both the 'alias' and 'related_alias'
- will not work in some conditions, like subquery pushdown.
-
- A parallel method is get_extra_descriptor_filter() which is used in
- instance.fieldname related object fetching.
- """
- return None
-
- def get_path_info(self):
- """
- Get path from this field to the related model.
- """
- opts = self.remote_field.model._meta
- from_opts = self.model._meta
- return [PathInfo(from_opts, opts, self.foreign_related_fields, self, False, True)]
-
- def get_reverse_path_info(self):
- """
- Get path from the related model to this field's model.
- """
- opts = self.model._meta
- 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):
- if lookup_name == 'in':
- return RelatedIn
- elif lookup_name == 'exact':
- return RelatedExact
- elif lookup_name == 'gt':
- return RelatedGreaterThan
- elif lookup_name == 'gte':
- return RelatedGreaterThanOrEqual
- elif lookup_name == 'lt':
- return RelatedLessThan
- elif lookup_name == 'lte':
- return RelatedLessThanOrEqual
- elif lookup_name == 'isnull':
- return RelatedIsNull
- else:
- raise TypeError('Related Field got invalid lookup: %s' % lookup_name)
-
- def get_transform(self, *args, **kwargs):
- raise NotImplementedError('Relational fields do not support transforms.')
-
- @property
- def attnames(self):
- return tuple(field.attname for field in self.local_related_fields)
-
- def get_defaults(self):
- return tuple(field.get_default() for field in self.local_related_fields)
-
- def contribute_to_class(self, cls, name, virtual_only=False):
- super(ForeignObject, self).contribute_to_class(cls, name, virtual_only=virtual_only)
- setattr(cls, self.name, ForwardManyToOneDescriptor(self))
-
- 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.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.remote_field.limit_choices_to:
- cls._meta.related_fkey_lookups.append(self.remote_field.limit_choices_to)
-
-
- class ForeignKey(ForeignObject):
- """
- Provide a many-to-one relation by adding a column to the local model
- to hold the remote value.
-
- By default ForeignKey will target the pk of the remote model but this
- behavior can be changed by using the ``to_field`` argument.
- """
-
- # Field flags
- many_to_many = False
- many_to_one = True
- one_to_many = False
- one_to_one = False
-
- rel_class = ManyToOneRel
-
- empty_strings_allowed = False
- default_error_messages = {
- 'invalid': _('%(model)s instance with %(field)s %(value)r does not exist.')
- }
- description = _("Foreign Key (type determined by related field)")
-
- def __init__(self, to, on_delete=None, related_name=None, related_query_name=None,
- limit_choices_to=None, parent_link=False, to_field=None,
- db_constraint=True, **kwargs):
- try:
- to._meta.model_name
- except AttributeError:
- assert isinstance(to, six.string_types), (
- "%s(%r) is invalid. First parameter to ForeignKey must be "
- "either a model, a model name, or the string %r" % (
- self.__class__.__name__, to,
- RECURSIVE_RELATIONSHIP_CONSTANT,
- )
- )
- else:
- # For backwards compatibility purposes, we need to *try* and set
- # the to_field during FK construction. It won't be guaranteed to
- # be correct until contribute_to_class is called. Refs #12190.
- to_field = to_field or (to._meta.pk and to._meta.pk.name)
-
- if on_delete is None:
- warnings.warn(
- "on_delete will be a required arg for %s in Django 2.0. Set "
- "it to models.CASCADE on models and in existing migrations "
- "if you want to maintain the current default behavior. "
- "See https://docs.djangoproject.com/en/%s/ref/models/fields/"
- "#django.db.models.ForeignKey.on_delete" % (
- self.__class__.__name__,
- get_docs_version(),
- ),
- RemovedInDjango20Warning, 2)
- on_delete = CASCADE
-
- elif not callable(on_delete):
- warnings.warn(
- "The signature for {0} will change in Django 2.0. "
- "Pass to_field='{1}' as a kwarg instead of as an arg.".format(
- self.__class__.__name__,
- on_delete,
- ),
- RemovedInDjango20Warning, 2)
- on_delete, to_field = to_field, on_delete
-
- kwargs['rel'] = self.rel_class(
- self, to, to_field,
- related_name=related_name,
- related_query_name=related_query_name,
- limit_choices_to=limit_choices_to,
- parent_link=parent_link,
- on_delete=on_delete,
- )
-
- kwargs['db_index'] = kwargs.get('db_index', True)
-
- super(ForeignKey, self).__init__(
- to, on_delete, from_fields=['self'], to_fields=[to_field], **kwargs)
-
- self.db_constraint = db_constraint
-
- def check(self, **kwargs):
- errors = super(ForeignKey, self).check(**kwargs)
- errors.extend(self._check_on_delete())
- errors.extend(self._check_unique())
- return errors
-
- def _check_on_delete(self):
- on_delete = getattr(self.remote_field, 'on_delete', None)
- if on_delete == SET_NULL and not self.null:
- return [
- checks.Error(
- 'Field specifies on_delete=SET_NULL, but cannot be null.',
- hint='Set null=True argument on the field, or change the on_delete rule.',
- obj=self,
- id='fields.E320',
- )
- ]
- elif on_delete == SET_DEFAULT and not self.has_default():
- return [
- checks.Error(
- 'Field specifies on_delete=SET_DEFAULT, but has no default value.',
- hint='Set a default value, or change the on_delete rule.',
- obj=self,
- id='fields.E321',
- )
- ]
- else:
- return []
-
- def _check_unique(self, **kwargs):
- return [
- checks.Warning(
- 'Setting unique=True on a ForeignKey has the same effect as using a OneToOneField.',
- hint='ForeignKey(unique=True) is usually better served by a OneToOneField.',
- obj=self,
- id='fields.W342',
- )
- ] if self.unique else []
-
- def deconstruct(self):
- name, path, args, kwargs = super(ForeignKey, self).deconstruct()
- del kwargs['to_fields']
- del kwargs['from_fields']
- # Handle the simpler arguments
- if self.db_index:
- del kwargs['db_index']
- else:
- kwargs['db_index'] = False
- if self.db_constraint is not True:
- kwargs['db_constraint'] = self.db_constraint
- # Rel needs more work.
- 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
- def target_field(self):
- return self.foreign_related_fields[0]
-
- def get_reverse_path_info(self):
- """
- Get path from the related model to this field's model.
- """
- opts = self.model._meta
- 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.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.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():
- raise exceptions.ValidationError(
- self.error_messages['invalid'],
- code='invalid',
- params={
- 'model': self.remote_field.model._meta.verbose_name, 'pk': value,
- 'field': self.remote_field.field_name, 'value': value,
- }, # 'pk' is included for backwards compatibility
- )
-
- def get_attname(self):
- return '%s_id' % self.name
-
- def get_attname_column(self):
- attname = self.get_attname()
- column = self.db_column or attname
- return attname, column
-
- 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.remote_field.model):
- return getattr(field_default, self.target_field.attname)
- return field_default
-
- def get_db_prep_save(self, value, connection):
- if value is None or (value == '' and
- (not self.target_field.empty_strings_allowed or
- connection.features.interprets_empty_strings_as_nulls)):
- return None
- else:
- return self.target_field.get_db_prep_save(value, connection=connection)
-
- def get_db_prep_value(self, value, connection, prepared=False):
- return self.target_field.get_db_prep_value(value, connection, prepared)
-
- def value_to_string(self, obj):
- if not obj:
- # In required many-to-one fields with only one available choice,
- # select that one available choice. Note: For SelectFields
- # we have to check that the length of choices is *2*, not 1,
- # because SelectFields always have an initial "blank" value.
- if not self.blank and self.choices:
- choice_list = self.get_choices_default()
- if len(choice_list) == 2:
- return smart_text(choice_list[1][0])
- return super(ForeignKey, self).value_to_string(obj)
-
- def contribute_to_related_class(self, cls, related):
- super(ForeignKey, self).contribute_to_related_class(cls, related)
- 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.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.remote_field.model))
- defaults = {
- 'form_class': forms.ModelChoiceField,
- '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)
-
- def db_type(self, connection):
- # The database column type of a ForeignKey is the column type
- # of the field to which it points. An exception is if the ForeignKey
- # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField,
- # in which case the column type is simply that of an IntegerField.
- # If the database needs similar types for key fields however, the only
- # thing we can do is making AutoField an IntegerField.
- rel_field = self.target_field
- if (isinstance(rel_field, AutoField) or
- (not connection.features.related_fields_match_type and
- isinstance(rel_field, (PositiveIntegerField,
- PositiveSmallIntegerField)))):
- return IntegerField().db_type(connection=connection)
- return rel_field.db_type(connection=connection)
-
- def db_parameters(self, connection):
- return {"type": self.db_type(connection), "check": []}
-
- def convert_empty_strings(self, value, expression, connection, context):
- if (not value) and isinstance(value, six.string_types):
- return None
- return value
-
- def get_db_converters(self, connection):
- converters = super(ForeignKey, self).get_db_converters(connection)
- if connection.features.interprets_empty_strings_as_nulls:
- converters += [self.convert_empty_strings]
- return converters
-
- def get_col(self, alias, output_field=None):
- return super(ForeignKey, self).get_col(alias, output_field or self.target_field)
-
-
- class OneToOneField(ForeignKey):
- """
- A OneToOneField is essentially the same as a ForeignKey, with the exception
- that it always carries a "unique" constraint with it and the reverse
- relation always returns the object pointed to (since there will only ever
- be one), rather than returning a list.
- """
-
- # Field flags
- many_to_many = False
- many_to_one = False
- one_to_many = False
- one_to_one = True
-
- related_accessor_class = ReverseOneToOneDescriptor
- rel_class = OneToOneRel
-
- description = _("One-to-one relationship")
-
- def __init__(self, to, on_delete=None, to_field=None, **kwargs):
- kwargs['unique'] = True
-
- if on_delete is None:
- warnings.warn(
- "on_delete will be a required arg for %s in Django 2.0. Set "
- "it to models.CASCADE on models and in existing migrations "
- "if you want to maintain the current default behavior. "
- "See https://docs.djangoproject.com/en/%s/ref/models/fields/"
- "#django.db.models.ForeignKey.on_delete" % (
- self.__class__.__name__,
- get_docs_version(),
- ),
- RemovedInDjango20Warning, 2)
- on_delete = CASCADE
-
- elif not callable(on_delete):
- warnings.warn(
- "The signature for {0} will change in Django 2.0. "
- "Pass to_field='{1}' as a kwarg instead of as an arg.".format(
- self.__class__.__name__,
- on_delete,
- ),
- RemovedInDjango20Warning, 2)
- to_field = on_delete
- on_delete = CASCADE # Avoid warning in superclass
-
- super(OneToOneField, self).__init__(to, on_delete, to_field=to_field, **kwargs)
-
- def deconstruct(self):
- name, path, args, kwargs = super(OneToOneField, self).deconstruct()
- if "unique" in kwargs:
- del kwargs['unique']
- return name, path, args, kwargs
-
- def formfield(self, **kwargs):
- 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.remote_field.model):
- setattr(instance, self.name, data)
- else:
- setattr(instance, self.attname, data)
-
- def _check_unique(self, **kwargs):
- # Override ForeignKey since check isn't applicable here.
- return []
-
-
- def create_many_to_many_intermediary_model(field, klass):
- from django.db import models
-
- def set_managed(model, related, through):
- through._meta.managed = model._meta.managed or related._meta.managed
-
- to_model = resolve_relation(klass, field.remote_field.model)
- name = '%s_%s' % (klass._meta.object_name, field.name)
- lazy_related_operation(set_managed, klass, to_model, name)
-
- to = make_model_tuple(to_model)[1]
- from_ = klass._meta.model_name
- if to == from_:
- to = 'to_%s' % to
- from_ = 'from_%s' % from_
-
- meta = type(str('Meta'), (object,), {
- 'db_table': field._get_m2m_db_table(klass._meta),
- 'auto_created': klass,
- 'app_label': klass._meta.app_label,
- 'db_tablespace': klass._meta.db_tablespace,
- 'unique_together': (from_, to),
- 'verbose_name': '%(from)s-%(to)s relationship' % {'from': from_, 'to': to},
- 'verbose_name_plural': '%(from)s-%(to)s relationships' % {'from': from_, 'to': to},
- 'apps': field.model._meta.apps,
- })
- # Construct and return the new class.
- return type(str(name), (models.Model,), {
- 'Meta': meta,
- '__module__': klass.__module__,
- from_: models.ForeignKey(
- klass,
- related_name='%s+' % name,
- db_tablespace=field.db_tablespace,
- db_constraint=field.remote_field.db_constraint,
- on_delete=CASCADE,
- ),
- to: models.ForeignKey(
- to_model,
- related_name='%s+' % name,
- db_tablespace=field.db_tablespace,
- db_constraint=field.remote_field.db_constraint,
- on_delete=CASCADE,
- )
- })
-
-
- class ManyToManyField(RelatedField):
- """
- Provide a many-to-many relation by using an intermediary model that
- holds two ForeignKey fields pointed at the two sides of the relation.
-
- Unless a ``through`` model was provided, ManyToManyField will use the
- create_many_to_many_intermediary_model factory to automatically generate
- the intermediary model.
- """
-
- # Field flags
- many_to_many = True
- many_to_one = False
- one_to_many = False
- one_to_one = False
-
- rel_class = ManyToManyRel
-
- description = _("Many-to-many relationship")
-
- def __init__(self, to, related_name=None, related_query_name=None,
- limit_choices_to=None, symmetrical=None, through=None,
- through_fields=None, db_constraint=True, db_table=None,
- swappable=True, **kwargs):
- try:
- to._meta
- except AttributeError:
- assert isinstance(to, six.string_types), (
- "%s(%r) is invalid. First parameter to ManyToManyField must be "
- "either a model, a model name, or the string %r" %
- (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
- )
- # Class names must be ASCII in Python 2.x, so we forcibly coerce it
- # here to break early if there's a problem.
- to = str(to)
-
- if symmetrical is None:
- symmetrical = (to == RECURSIVE_RELATIONSHIP_CONSTANT)
-
- if through is not None:
- assert db_table is None, (
- "Cannot specify a db_table if an intermediary model is used."
- )
-
- kwargs['rel'] = self.rel_class(
- self, to,
- related_name=related_name,
- related_query_name=related_query_name,
- limit_choices_to=limit_choices_to,
- symmetrical=symmetrical,
- through=through,
- 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
-
- def check(self, **kwargs):
- errors = super(ManyToManyField, self).check(**kwargs)
- errors.extend(self._check_unique(**kwargs))
- errors.extend(self._check_relationship_model(**kwargs))
- errors.extend(self._check_ignored_options(**kwargs))
- return errors
-
- def _check_unique(self, **kwargs):
- if self.unique:
- return [
- checks.Error(
- 'ManyToManyFields cannot be unique.',
- hint=None,
- obj=self,
- id='fields.E330',
- )
- ]
- return []
-
- def _check_ignored_options(self, **kwargs):
- warnings = []
-
- if self.has_null_arg:
- warnings.append(
- checks.Warning(
- 'null has no effect on ManyToManyField.',
- hint=None,
- obj=self,
- id='fields.W340',
- )
- )
-
- if len(self._validators) > 0:
- warnings.append(
- checks.Warning(
- 'ManyToManyField does not support validators.',
- hint=None,
- obj=self,
- id='fields.W341',
- )
- )
-
- return warnings
-
- def _check_relationship_model(self, from_model=None, **kwargs):
- if hasattr(self.remote_field.through, '_meta'):
- qualified_model_name = "%s.%s" % (
- self.remote_field.through._meta.app_label, self.remote_field.through.__name__)
- else:
- qualified_model_name = self.remote_field.through
-
- errors = []
-
- if self.remote_field.through not in self.opts.apps.get_models(include_auto_created=True):
- # The relationship model is not installed.
- errors.append(
- checks.Error(
- ("Field specifies a many-to-many relation through model "
- "'%s', which has not been installed.") %
- qualified_model_name,
- hint=None,
- obj=self,
- id='fields.E331',
- )
- )
-
- else:
-
- assert from_model is not None, (
- "ManyToManyField with intermediate "
- "tables cannot be checked if you don't pass the model "
- "where the field is attached to."
- )
-
- # Set some useful local variables
- to_model = resolve_relation(from_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.remote_field.through._meta.object_name
- self_referential = from_model == to_model
-
- # Check symmetrical attribute.
- 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.',
- hint=None,
- obj=self,
- id='fields.E332',
- )
- )
-
- # Count foreign keys in intermediate model
- if self_referential:
- 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.remote_field.through_fields:
- errors.append(
- checks.Error(
- ("The model is used as an intermediate model by "
- "'%s', but it has more than two foreign keys "
- "to '%s', which is ambiguous. You must specify "
- "which two foreign keys Django should use via the "
- "through_fields keyword argument.") % (self, from_model_name),
- hint=("Use through_fields to specify which two "
- "foreign keys Django should use."),
- obj=self.remote_field.through,
- id='fields.E333',
- )
- )
-
- else:
- # Count foreign keys in relationship model
- 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.remote_field.through_fields:
- errors.append(
- checks.Error(
- ("The model is used as an intermediate model by "
- "'%s', but it has more than one foreign key "
- "from '%s', which is ambiguous. You must specify "
- "which foreign key Django should use via the "
- "through_fields keyword argument.") % (self, from_model_name),
- hint=('If you want to create a recursive relationship, '
- 'use ForeignKey("self", symmetrical=False, '
- 'through="%s").') % relationship_model_name,
- obj=self,
- id='fields.E334',
- )
- )
-
- if seen_to > 1 and not self.remote_field.through_fields:
- errors.append(
- checks.Error(
- ("The model is used as an intermediate model by "
- "'%s', but it has more than one foreign key "
- "to '%s', which is ambiguous. You must specify "
- "which foreign key Django should use via the "
- "through_fields keyword argument.") % (self, to_model_name),
- hint=('If you want to create a recursive '
- 'relationship, use ForeignKey("self", '
- 'symmetrical=False, through="%s").') % relationship_model_name,
- obj=self,
- id='fields.E335',
- )
- )
-
- if seen_from == 0 or seen_to == 0:
- errors.append(
- checks.Error(
- ("The model is used as an intermediate model by "
- "'%s', but it does not have a foreign key to '%s' or '%s'.") % (
- self, from_model_name, to_model_name
- ),
- hint=None,
- obj=self.remote_field.through,
- id='fields.E336',
- )
- )
-
- # Validate `through_fields`.
- 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.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 "
- "provide the names of the two link fields that should be "
- "used for the relation through model "
- "'%s'.") % qualified_model_name,
- hint=("Make sure you specify 'through_fields' as "
- "through_fields=('field1', 'field2')"),
- obj=self,
- id='fields.E337',
- )
- )
-
- # Validate the given through fields -- they should be actual
- # fields on the through model, and also be foreign keys to the
- # expected models.
- else:
- assert from_model is not None, (
- "ManyToManyField with intermediate "
- "tables cannot be checked if you don't pass the model "
- "where the field is attached to."
- )
-
- 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, '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 "
- "keys to '%s': %s?") % (related_model._meta.object_name,
- ', '.join(possible_field_names))
- else:
- hint = None
-
- try:
- field = through._meta.get_field(field_name)
- except exceptions.FieldDoesNotExist:
- errors.append(
- checks.Error(
- ("The intermediary model '%s' has no field '%s'.") % (
- qualified_model_name, field_name),
- hint=hint,
- obj=self,
- id='fields.E338',
- )
- )
- else:
- 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'." % (
- through._meta.object_name, field_name,
- related_model._meta.object_name),
- hint=hint,
- obj=self,
- id='fields.E339',
- )
- )
-
- return errors
-
- def deconstruct(self):
- name, path, args, kwargs = super(ManyToManyField, self).deconstruct()
- # Handle the simpler arguments.
- if self.db_table is not None:
- kwargs['db_table'] = self.db_table
- 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.remote_field.model, six.string_types):
- kwargs['to'] = self.remote_field.model
- else:
- 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
- if swappable_setting is not None:
- # If it's already a settings reference, error.
- if hasattr(kwargs['to'], "setting_name"):
- if kwargs['to'].setting_name != swappable_setting:
- raise ValueError(
- "Cannot deconstruct a ManyToManyField pointing to a "
- "model that is swapped in place of more than one model "
- "(%s and %s)" % (kwargs['to'].setting_name, swappable_setting)
- )
-
- from django.db.migrations.writer import SettingsReference
- kwargs['to'] = SettingsReference(
- kwargs['to'],
- swappable_setting,
- )
- return name, path, args, kwargs
-
- def _get_path_info(self, direct=False):
- """
- Called by both direct and indirect m2m traversal.
- """
- pathinfos = []
- 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:
- join1infos = linkfield1.get_reverse_path_info()
- join2infos = linkfield2.get_path_info()
- else:
- join1infos = linkfield2.get_reverse_path_info()
- join2infos = linkfield1.get_path_info()
- pathinfos.extend(join1infos)
- pathinfos.extend(join2infos)
- return pathinfos
-
- def get_path_info(self):
- return self._get_path_info(direct=True)
-
- def get_reverse_path_info(self):
- return self._get_path_info(direct=False)
-
- def get_choices_default(self):
- return Field.get_choices(self, include_blank=False)
-
- def _get_m2m_db_table(self, opts):
- """
- Function that can be curried to provide the m2m table name for this
- relation.
- """
- 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:
- return utils.truncate_name('%s_%s' % (opts.db_table, self.name),
- connection.ops.max_name_length())
-
- def _get_m2m_attr(self, related, attr):
- """
- Function that can be curried to provide the source accessor or DB
- column name for the m2m table.
- """
- cache_attr = '_m2m_%s_cache' % attr
- if hasattr(self, cache_attr):
- return getattr(self, cache_attr)
- 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.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)
-
- def _get_m2m_reverse_attr(self, related, attr):
- """
- Function that can be curried to provide the related accessor or DB
- column name for the m2m table.
- """
- cache_attr = '_m2m_reverse_%s_cache' % attr
- if hasattr(self, cache_attr):
- return getattr(self, cache_attr)
- found = False
- 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.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
- # the source column. Keep searching for
- # the second foreign key.
- if found:
- setattr(self, cache_attr, getattr(f, attr))
- break
- else:
- found = True
- elif link_field_name is None or link_field_name == f.name:
- setattr(self, cache_attr, getattr(f, attr))
- break
- return getattr(self, cache_attr)
-
- def value_to_string(self, obj):
- data = ''
- if obj:
- qs = getattr(obj, self.name).all()
- data = [instance._get_pk_val() for instance in qs]
- else:
- # In required many-to-many fields with only one available choice,
- # select that one available choice.
- if not self.blank:
- choices_list = self.get_choices_default()
- if len(choices_list) == 1:
- data = [choices_list[0][0]]
- return smart_text(data)
-
- def contribute_to_class(self, cls, name, **kwargs):
- # To support multiple relations to self, it's useful to have a non-None
- # related name on symmetrical relations for internal reasons. The
- # concept doesn't make a lot of sense externally ("you want me to
- # 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.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
- elif self.remote_field.is_hidden():
- # If the backwards relation is disabled, replace the original
- # related_name with one generated from the m2m field name. Django
- # still uses backwards relations internally and we need to avoid
- # clashes between multiple m2m fields with related_name == '+'.
- self.remote_field.related_name = "_%s_%s_+" % (cls.__name__.lower(), name)
-
- super(ManyToManyField, self).contribute_to_class(cls, name, **kwargs)
-
- # The intermediate m2m model is not auto created if:
- # 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 cls._meta.abstract:
- if self.remote_field.through:
- def resolve_through_model(_, model, field):
- field.remote_field.through = model
- lazy_related_operation(resolve_through_model, cls, self.remote_field.through, field=self)
- elif 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, ManyToManyDescriptor(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)
-
- 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.remote_field.is_hidden() and not related.related_model._meta.swapped:
- setattr(cls, related.get_accessor_name(), ManyToManyDescriptor(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')
- self.m2m_reverse_name = curry(self._get_m2m_reverse_attr, related, 'column')
-
- 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, 'remote_field')
- self.m2m_target_field_name = lambda: get_m2m_rel().field_name
- 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):
- pass
-
- def value_from_object(self, obj):
- """
- Return the value of this field in the given model instance.
- """
- return getattr(obj, self.attname).all()
-
- def save_form_data(self, instance, data):
- setattr(instance, self.attname, data)
-
- def formfield(self, **kwargs):
- db = kwargs.pop('using', None)
- defaults = {
- 'form_class': forms.ModelMultipleChoiceField,
- '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
- # MultipleChoiceField takes a list of IDs.
- if defaults.get('initial') is not None:
- initial = defaults['initial']
- if callable(initial):
- initial = initial()
- defaults['initial'] = [i._get_pk_val() for i in initial]
- return super(ManyToManyField, self).formfield(**defaults)
-
- def db_type(self, connection):
- # A ManyToManyField is not represented by a single column,
- # so return None.
- return None
-
- def db_parameters(self, connection):
- return {"type": None, "check": None}
|