Source code for django_sorcery.db.meta.relations

"""Metadata for sqlalchemy model relationships."""
from itertools import chain

import sqlalchemy as sa
from django.core.exceptions import ImproperlyConfigured


[docs]class relation_info: """A helper class that makes sqlalchemy relationship property inspection easier.""" __slots__ = ( "attribute", "direction", "field_kwargs", "foreign_keys", "local_remote_pairs", "local_remote_pairs_for_identity_key", "name", "parent_mapper", "parent_model", "parent_table", "related_mapper", "related_model", "related_table", "relationship", "uselist", ) def __init__(self, relationship): self.relationship = relationship self.name = self.relationship.key self.parent_mapper = self.relationship.parent self.parent_model = self.relationship.parent.class_ self.parent_table = self.relationship.parent.tables[0] self.related_mapper = self.relationship.mapper self.related_model = self.relationship.mapper.class_ self.related_table = self.relationship.mapper.tables[0] self.attribute = getattr(self.parent_model, self.name) self.direction = self.relationship.direction self.foreign_keys = list( set(chain(self.relationship._calculated_foreign_keys, self.relationship._user_defined_foreign_keys)) ) self.uselist = self.relationship.uselist self.field_kwargs = {} if self.uselist: self.field_kwargs["required"] = False else: self.field_kwargs["required"] = not all(col.nullable for col in self.foreign_keys) self.local_remote_pairs = self.relationship.local_remote_pairs target_pk = list(self.relationship.target.primary_key) pairs = {v: k for k, v in self.local_remote_pairs} self.local_remote_pairs_for_identity_key = [] try: # ensure local_remote pairs are of same order as remote pk for i in target_pk: self.local_remote_pairs_for_identity_key.append((pairs[i], i)) except KeyError: # if relation is missing one of related pk columns # but actual constraint has it defined # attempt to deduce what is the missing pk column # by inspecting FK constraint on table object itself # this only happens for pretty bad table structure # where some columns need to be omitted from relation # since same column is used in multiple relations # for people reading this comment lesson should be # DO NOT USE COMPOSITE PKs parent_columns = set(pairs.values()) target_columns = set(target_pk) matching_constraints = [ i for i in [c for c in self.parent_table.constraints if isinstance(c, sa.ForeignKeyConstraint)] if parent_columns & {j.parent for j in i.elements} == parent_columns and target_columns & {j.column for j in i.elements} == target_columns ] if len(matching_constraints) == 1: pairs = {i.column: i.parent for i in matching_constraints[0].elements} for i in target_pk: self.local_remote_pairs_for_identity_key.append((pairs[i], i)) else: # if everything fails, return default pairs self.local_remote_pairs_for_identity_key = self.local_remote_pairs[:]
[docs] def formfield(self, form_class=None, **kwargs): if kwargs.get("session") is None: raise ImproperlyConfigured("Creating a relation form field without session attribute prohibited") form_class = form_class or self.get_form_class() if form_class is not None: field_kwargs = self.field_kwargs.copy() field_kwargs.update(kwargs) return form_class(self.related_model, **field_kwargs)
[docs] def get_form_class(self): from ...fields import ModelChoiceField, ModelMultipleChoiceField if self.uselist: return ModelMultipleChoiceField return ModelChoiceField
def __repr__(self): return "<relation_info({}.{})>".format(self.parent_model.__name__, self.name)