forked from encode/django-rest-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmodel_meta.py
156 lines (125 loc) · 4.95 KB
/
model_meta.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""
Helper function for returning the field information that is associated
with a model class. This includes returning all the forward and reverse
relationships and their associated metadata.
Usage: `get_field_info(model)` returns a `FieldInfo` instance.
"""
from collections import namedtuple
FieldInfo = namedtuple('FieldInfo', [
'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',
'to_many',
'to_field',
'has_through_model',
'reverse'
])
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.
"""
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):
pk = opts.pk
rel = pk.remote_field
while rel and rel.parent_link:
# If model is a child via multi-table inheritance, use parent's pk.
pk = pk.remote_field.model._meta.pk
rel = pk.remote_field
return pk
def _get_fields(opts):
fields = {}
for field in [field for field in opts.fields if field.serialize and not field.remote_field]:
fields[field.name] = field
return fields
def _get_to_field(field):
return getattr(field, 'to_fields', None) and field.to_fields[0]
def _get_forward_relationships(opts):
"""
Returns a dict of field names to `RelationInfo`.
"""
forward_relations = {}
for field in [field for field in opts.fields if field.serialize and field.remote_field]:
forward_relations[field.name] = RelationInfo(
model_field=field,
related_model=field.remote_field.model,
to_many=False,
to_field=_get_to_field(field),
has_through_model=False,
reverse=False
)
# 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(
model_field=field,
related_model=field.remote_field.model,
to_many=True,
# manytomany do not have to_fields
to_field=None,
has_through_model=(
not field.remote_field.through._meta.auto_created
),
reverse=False
)
return forward_relations
def _get_reverse_relationships(opts):
"""
Returns a dict of field names to `RelationInfo`.
"""
reverse_relations = {}
all_related_objects = [r for r in opts.related_objects if not r.field.many_to_many]
for relation in all_related_objects:
accessor_name = relation.get_accessor_name()
reverse_relations[accessor_name] = RelationInfo(
model_field=None,
related_model=relation.related_model,
to_many=relation.field.remote_field.multiple,
to_field=_get_to_field(relation.field),
has_through_model=False,
reverse=True
)
# 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:
accessor_name = relation.get_accessor_name()
reverse_relations[accessor_name] = RelationInfo(
model_field=None,
related_model=relation.related_model,
to_many=True,
# manytomany do not have to_fields
to_field=None,
has_through_model=(
(getattr(relation.field.remote_field, 'through', None) is not None) and
not relation.field.remote_field.through._meta.auto_created
),
reverse=True
)
return reverse_relations
def _merge_fields_and_pk(pk, fields):
fields_and_pk = {'pk': pk, pk.name: pk}
fields_and_pk.update(fields)
return fields_and_pk
def _merge_relationships(forward_relations, reverse_relations):
return {**forward_relations, **reverse_relations}
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