-
Notifications
You must be signed in to change notification settings - Fork 142
/
utils.py
114 lines (92 loc) · 3.4 KB
/
utils.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
# -*- coding: utf-8 -*-
from functools import reduce
from django.utils.text import smart_split
MINIMUM_PAGE_LENGTH = 1
DEFAULT_EMPTY_VALUE = ""
DEFAULT_MULTIPLE_SEPARATOR = " "
# Since it's rather painful to deal with the datatables.js naming scheme in Python, this map changes
# the Pythonic names to the javascript ones in the GET request
OPTION_NAME_MAP = {
"start_offset": "start",
"page_length": "length",
"search": "search[value]",
"search_column": "columns[%d][search][value]",
"sort_column": "order[%d][column]",
"sort_column_direction": "order[%d][dir]",
}
# Mapping of Django's supported field types to their more generic type names.
# These values are primarily used for the xeditable field type lookups.
# TODO: Would be nice if we can derive these from FIELD_TYPES so there's less repetition.
XEDITABLE_FIELD_TYPES = {
"AutoField": "number",
"BooleanField": "text",
"CharField": "text",
"CommaSeparatedIntegerField": "text",
"DateField": "date",
"DateTimeField": "datetime",
"DecimalField": "text",
"FileField": "text",
"FilePathField": "text",
"FloatField": "number",
"IntegerField": "number",
"BigIntegerField": "number",
"IPAddressField": "text",
"GenericIPAddressField": "text",
"NullBooleanField": "text",
"PositiveIntegerField": "number",
"PositiveSmallIntegerField": "number",
"SlugField": "text",
"SmallIntegerField": "number",
"TextField": "text",
"TimeField": "text",
"ForeignKey": "select",
}
def resolve_orm_path(model, orm_path):
"""
Follows the queryset-style query path of ``orm_path`` starting from ``model`` class. If the
path ends up referring to a bad field name, ``django.db.models.fields.FieldDoesNotExist`` will
be raised.
"""
bits = orm_path.split("__")
endpoint_model = reduce(get_model_at_related_field, [model] + bits[:-1])
if bits[-1] == "pk":
field = endpoint_model._meta.pk
else:
field = endpoint_model._meta.get_field(bits[-1])
return field
def get_model_at_related_field(model, attr):
"""
Looks up ``attr`` as a field of ``model`` and returns the related model class. If ``attr`` is
not a relationship field, ``ValueError`` is raised.
"""
field = model._meta.get_field(attr)
if hasattr(field, "related_model"):
return field.related_model
raise ValueError(
"{model}.{attr} ({klass}) is not a relationship field.".format(
**{
"model": model.__name__,
"attr": attr,
"klass": field.__class__.__name__,
}
)
)
def get_first_orm_bit(column):
"""Returns the first ORM path component of a field definition's declared db field."""
if not column.sources:
return None
return column.sources[0].split("__")[0]
def contains_plural_field(model, fields):
"""Returns a boolean indicating if ``fields`` contains a relationship to multiple items."""
source_model = model
for orm_path in fields:
model = source_model
bits = orm_path.lstrip("+-").split("__")
for bit in bits[:-1]:
field = model._meta.get_field(bit)
if field.many_to_many or field.one_to_many:
return True
model = get_model_at_related_field(model, bit)
return False
def split_terms(s):
return filter(None, map(lambda t: t.strip("'\" "), smart_split(s)))