django-rest-framework/rest_framework/utils/model_meta.py

175 lines
5.7 KiB
Python
Raw Normal View History

2014-09-09 20:46:28 +04:00
"""
2014-09-18 14:20:56 +04:00
Helper function for returning the field information that is associated
2014-09-12 12:12:56 +04:00
with a model class. This includes returning all the forward and reverse
relationships and their associated metadata.
2014-09-18 14:20:56 +04:00
Usage: `get_field_info(model)` returns a `FieldInfo` instance.
2014-09-09 20:46:28 +04:00
"""
2015-09-22 17:35:38 +03:00
from collections import OrderedDict, namedtuple
from rest_framework.compat import get_related_model, get_remote_field
2014-09-18 14:20:56 +04:00
FieldInfo = namedtuple('FieldResult', [
'pk', # Model field instance
'fields', # Dict of field name -> model field instance
'forward_relations', # Dict of field name -> RelationInfo
'reverse_relations', # Dict of field name -> RelationInfo
'fields_and_pk', # Shortcut for 'pk' + 'fields'
'relations' # Shortcut for 'forward_relations' + 'reverse_relations'
])
RelationInfo = namedtuple('RelationInfo', [
'model_field',
'related_model',
2014-09-18 14:20:56 +04:00
'to_many',
'to_field',
'has_through_model',
'reverse'
2014-09-18 14:20:56 +04:00
])
2014-09-09 20:46:28 +04:00
def get_field_info(model):
"""
Given a model class, returns a `FieldInfo` instance, which is a
`namedtuple`, containing metadata about the various field types on the model
including information about their relationships.
2014-09-09 20:46:28 +04:00
"""
opts = model._meta.concrete_model._meta
pk = _get_pk(opts)
fields = _get_fields(opts)
forward_relations = _get_forward_relationships(opts)
reverse_relations = _get_reverse_relationships(opts)
fields_and_pk = _merge_fields_and_pk(pk, fields)
relationships = _merge_relationships(forward_relations, reverse_relations)
return FieldInfo(pk, fields, forward_relations, reverse_relations,
fields_and_pk, relationships)
def _get_pk(opts):
2014-09-09 20:46:28 +04:00
pk = opts.pk
rel = get_remote_field(pk)
while rel and rel.parent_link:
# If model is a child via multi-table inheritance, use parent's pk.
pk = get_related_model(pk)._meta.pk
rel = get_remote_field(pk)
2014-09-09 20:46:28 +04:00
return pk
def _get_fields(opts):
fields = OrderedDict()
for field in [field for field in opts.fields if field.serialize and not get_remote_field(field)]:
2014-09-09 20:46:28 +04:00
fields[field.name] = field
return fields
def _get_to_field(field):
2015-11-15 07:04:32 +03:00
return getattr(field, 'to_fields', None) and field.to_fields[0]
def _get_forward_relationships(opts):
"""
Returns an `OrderedDict` of field names to `RelationInfo`.
"""
forward_relations = OrderedDict()
for field in [
field for field in opts.fields
if field.serialize and get_remote_field(field) and not (field.primary_key and field.one_to_one)
# If the field is a OneToOneField and it's been marked as PK, then this
# is a multi-table inheritance auto created PK ('%_ptr').
]:
2014-09-09 20:46:28 +04:00
forward_relations[field.name] = RelationInfo(
2014-09-18 14:20:56 +04:00
model_field=field,
related_model=get_related_model(field),
2014-09-09 20:46:28 +04:00
to_many=False,
to_field=_get_to_field(field),
has_through_model=False,
reverse=False
2014-09-09 20:46:28 +04:00
)
# Deal with forward many-to-many relationships.
for field in [field for field in opts.many_to_many if field.serialize]:
forward_relations[field.name] = RelationInfo(
2014-09-18 14:20:56 +04:00
model_field=field,
related_model=get_related_model(field),
2014-09-09 20:46:28 +04:00
to_many=True,
# manytomany do not have to_fields
to_field=None,
2014-09-09 20:46:28 +04:00
has_through_model=(
not get_remote_field(field).through._meta.auto_created
),
reverse=False
2014-09-09 20:46:28 +04:00
)
return forward_relations
def _get_reverse_relationships(opts):
"""
Returns an `OrderedDict` of field names to `RelationInfo`.
"""
2015-01-23 14:15:11 +03:00
# Note that we have a hack here to handle internal API differences for
# this internal API across Django 1.7 -> Django 1.8.
# See: https://code.djangoproject.com/ticket/24208
reverse_relations = OrderedDict()
all_related_objects = [r for r in opts.related_objects if not r.field.many_to_many]
for relation in all_related_objects:
2014-09-09 20:46:28 +04:00
accessor_name = relation.get_accessor_name()
2015-01-23 14:15:11 +03:00
related = getattr(relation, 'related_model', relation.model)
2014-09-09 20:46:28 +04:00
reverse_relations[accessor_name] = RelationInfo(
2014-09-18 14:20:56 +04:00
model_field=None,
2015-01-23 17:28:59 +03:00
related_model=related,
to_many=get_remote_field(relation.field).multiple,
to_field=_get_to_field(relation.field),
has_through_model=False,
reverse=True
2014-09-09 20:46:28 +04:00
)
# Deal with reverse many-to-many relationships.
all_related_many_to_many_objects = [r for r in opts.related_objects if r.field.many_to_many]
for relation in all_related_many_to_many_objects:
2014-09-09 20:46:28 +04:00
accessor_name = relation.get_accessor_name()
2015-01-23 14:15:11 +03:00
related = getattr(relation, 'related_model', relation.model)
2014-09-09 20:46:28 +04:00
reverse_relations[accessor_name] = RelationInfo(
2014-09-18 14:20:56 +04:00
model_field=None,
2015-01-23 17:28:59 +03:00
related_model=related,
2014-09-09 20:46:28 +04:00
to_many=True,
# manytomany do not have to_fields
to_field=None,
2014-09-09 20:46:28 +04:00
has_through_model=(
(getattr(get_remote_field(relation.field), 'through', None) is not None) and
not get_remote_field(relation.field).through._meta.auto_created
),
reverse=True
2014-09-09 20:46:28 +04:00
)
return reverse_relations
def _merge_fields_and_pk(pk, fields):
fields_and_pk = OrderedDict()
2014-09-18 14:20:56 +04:00
fields_and_pk['pk'] = pk
fields_and_pk[pk.name] = pk
fields_and_pk.update(fields)
return fields_and_pk
2014-09-18 14:20:56 +04:00
def _merge_relationships(forward_relations, reverse_relations):
return OrderedDict(
2014-09-18 14:20:56 +04:00
list(forward_relations.items()) +
list(reverse_relations.items())
)
def is_abstract_model(model):
"""
Given a model class, returns a boolean True if it is abstract and False if it is not.
"""
return hasattr(model, '_meta') and hasattr(model._meta, 'abstract') and model._meta.abstract