blob: 0c34d3e587fcbed9030603eb5c80a10937f7a444 [file] [log] [blame]
Matteo Scandolod2044a42017-08-07 16:08:28 -07001# Copyright 2017-present Open Networking Foundation
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Zack Williams9a42f872019-02-15 17:56:04 -070015from __future__ import absolute_import, print_function
16from .base import unquote
Sapan Bhatia1e021772017-08-19 02:15:48 -040017import re
Zack Williams00e22d62019-03-01 22:32:13 -070018import sys
Zack Williams9a42f872019-02-15 17:56:04 -070019from six.moves import map
Sapan Bhatia5ea307d2017-07-19 00:13:21 -040020
Zack Williams045b63d2019-01-22 16:30:57 -070021
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040022def django_content_type_string(xptags):
23 # Check possibility of KeyError in caller
Zack Williams045b63d2019-01-22 16:30:57 -070024 content_type = xptags["content_type"]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040025
26 try:
27 content_type = eval(content_type)
Zack Williams045b63d2019-01-22 16:30:57 -070028 except BaseException:
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040029 pass
30
Zack Williams045b63d2019-01-22 16:30:57 -070031 if content_type == "url":
32 return "URLField"
33 if content_type == "date":
34 return "DateTimeField"
35 elif content_type == "ip":
36 return "GenericIPAddressField"
37 elif content_type == "stripped" or content_type == '"stripped"':
38 return "StrippedCharField"
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040039 else:
Zack Williams045b63d2019-01-22 16:30:57 -070040 raise Exception("Unknown Type: %s" % content_type)
41
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040042
43def django_string_type(xptags):
44 try:
Zack Williams045b63d2019-01-22 16:30:57 -070045 max_length = eval(xptags["max_length"])
46 except BaseException:
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040047 max_length = 1024 * 1024
48
Zack Williams045b63d2019-01-22 16:30:57 -070049 if "content_type" in xptags:
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040050 return django_content_type_string(xptags)
Zack Williams9a42f872019-02-15 17:56:04 -070051 elif int(max_length) < 1024 * 1024:
Zack Williams045b63d2019-01-22 16:30:57 -070052 return "CharField"
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040053 else:
Zack Williams045b63d2019-01-22 16:30:57 -070054 return "TextField"
55
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040056
57def xproto_django_type(xptype, xptags):
Zack Williams045b63d2019-01-22 16:30:57 -070058 if xptype == "string":
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040059 return django_string_type(xptags)
Zack Williams045b63d2019-01-22 16:30:57 -070060 elif xptype == "float":
61 return "FloatField"
62 elif xptype == "bool":
63 return "BooleanField"
64 elif xptype == "uint32":
65 return "IntegerField"
66 elif xptype == "int32":
67 return "IntegerField"
68 elif xptype == "int64":
69 return "BigIntegerField"
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040070 else:
Zack Williams045b63d2019-01-22 16:30:57 -070071 raise Exception("Unknown Type: %s" % xptype)
72
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040073
74def xproto_django_link_type(f):
Zack Williams045b63d2019-01-22 16:30:57 -070075 if f["link_type"] == "manytoone":
76 return "ForeignKey"
77 elif f["link_type"] == "onetoone":
78 return "OneToOneField"
79 elif f["link_type"] == "manytomany":
80 if f["dst_port"]:
81 return "ManyToManyField"
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040082 else:
Zack Williams045b63d2019-01-22 16:30:57 -070083 return "GenericRelation"
84
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040085
86def map_xproto_to_django(f):
Zack Williams00e22d62019-03-01 22:32:13 -070087
Zack Williams045b63d2019-01-22 16:30:57 -070088 allowed_keys = [
Zack Williams00e22d62019-03-01 22:32:13 -070089 "auto_now_add",
Zack Williams045b63d2019-01-22 16:30:57 -070090 "blank",
91 "choices",
92 "db_index",
Zack Williams00e22d62019-03-01 22:32:13 -070093 "default",
Zack Williams045b63d2019-01-22 16:30:57 -070094 "editable",
Zack Williams00e22d62019-03-01 22:32:13 -070095 "help_text",
96 "max_length",
Zack Williams045b63d2019-01-22 16:30:57 -070097 "max_value",
Zack Williams00e22d62019-03-01 22:32:13 -070098 "min_value",
99 "null",
100 "on_delete",
101 "unique",
102 "verbose_name",
Zack Williams045b63d2019-01-22 16:30:57 -0700103 ]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400104
Zack Williams00e22d62019-03-01 22:32:13 -0700105 out = {} # output dictionary
Scott Baker2e856be2019-02-07 09:28:09 -0800106
Zack Williams00e22d62019-03-01 22:32:13 -0700107 # filter options dict to only have allowed keys
Zack Williams045b63d2019-01-22 16:30:57 -0700108 for k, v in f["options"].items():
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400109 if k in allowed_keys:
Zack Williams00e22d62019-03-01 22:32:13 -0700110 out[k] = v
111
112 # deal with optional/required modifier fields, and manytomany links
113 # modifier is not added to "out" dict, but affects blank/null truth
114 modifier = f["options"].get('modifier')
115 link_type = f.get("link_type")
116
117 # in some tests, there is no field type
118 if "type" in f:
119 field_type = f["type"]
120 else:
121 field_type = None
122
123 mod_out = {}
124
125 if modifier == "required":
126
127 if field_type == "string":
128 if "blank" not in out: # if blank is already set, honor that value
129 mod_out["blank"] = 'True' # by default, required strings can be blank
130 else:
131 mod_out["blank"] = 'False' # but other required fields can't be blank
132
133 if link_type != "manytomany":
134 mod_out["null"] = 'False'
135
136 elif modifier == "optional":
137
138 mod_out["blank"] = 'True'
139
140 # set defaults on link types
141 if link_type != "manytomany" and field_type != "bool":
142 mod_out["null"] = 'True'
143
144 else:
145 print("map_xproto_to_django - unknown modifier type: %s on %s" % (modifier, f), file=sys.stderr)
146
147 # print an error if there's a field conflict
148 for kmo in mod_out.keys():
149 if kmo in out:
150 if out[kmo] != mod_out[kmo]:
151 print("Option '%s' is manually set to value '%s', which "
152 "conflicts with value '%s' set automatically by modifier on field: %s" %
153 (kmo, out[kmo], mod_out[kmo], f), file=sys.stderr)
154
155 out.update(mod_out) # overwrite out keys with mod_out
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400156
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400157 return out
158
Zack Williams045b63d2019-01-22 16:30:57 -0700159
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400160def xproto_django_link_options_str(field, dport=None):
Scott Baker2e856be2019-02-07 09:28:09 -0800161 # Note that this function is called for links (ForeignKeys, M2Ms)
162
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400163 output_dict = map_xproto_to_django(field)
164
Zack Williams045b63d2019-01-22 16:30:57 -0700165 if dport and (dport == "+" or "+" not in dport):
166 output_dict["related_name"] = "%r" % dport
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400167
168 try:
Zack Williams045b63d2019-01-22 16:30:57 -0700169 if field["through"]:
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400170 d = {}
Zack Williams045b63d2019-01-22 16:30:57 -0700171 if isinstance(field["through"], str):
172 split = field["through"].rsplit(".", 1)
173 d["name"] = split[-1]
174 if len(split) == 2:
175 d["package"] = split[0]
176 d["fqn"] = "package" + "." + d["name"]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400177 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700178 d["fqn"] = d["name"]
179 d["package"] = ""
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400180 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700181 d = field["through"]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400182
Zack Williams045b63d2019-01-22 16:30:57 -0700183 if not d["name"].endswith("_" + field["name"]):
184 output_dict["through"] = "%r" % d["fqn"]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400185 except KeyError:
186 pass
187
188 return format_options_string(output_dict)
189
Zack Williams045b63d2019-01-22 16:30:57 -0700190
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400191def use_native_django_validators(k, v):
192
193 validators_map = {
Zack Williams045b63d2019-01-22 16:30:57 -0700194 "min_value": "MinValueValidator",
195 "max_value": "MaxValueValidator",
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400196 }
197
198 return "%s(%s)" % (validators_map[k], v)
199
Zack Williams045b63d2019-01-22 16:30:57 -0700200
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400201def format_options_string(d):
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400202
Zack Williams045b63d2019-01-22 16:30:57 -0700203 known_validators = ["min_value", "max_value"]
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400204 validator_lst = []
205
Zack Williams045b63d2019-01-22 16:30:57 -0700206 if not d:
207 return ""
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400208 else:
209
210 lst = []
Zack Williams9a42f872019-02-15 17:56:04 -0700211 for k, v in sorted(d.items(), key=lambda t: t[0]): # sorted by key
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400212 if k in known_validators:
213 validator_lst.append(use_native_django_validators(k, v))
Zack Williams045b63d2019-01-22 16:30:57 -0700214 elif isinstance(v, str) and k == "default" and v.endswith('()"'):
215 lst.append("%s = %s" % (k, v[1:-3]))
216 elif isinstance(v, str) and v.startswith('"'):
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400217 try:
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400218 # unquote the value if necessary
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400219 tup = eval(v[1:-1])
Zack Williams045b63d2019-01-22 16:30:57 -0700220 if isinstance(tup, tuple):
221 lst.append("%s = %r" % (k, tup))
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400222 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700223 lst.append("%s = %s" % (k, v))
224 except BaseException:
225 lst.append("%s = %s" % (k, v))
226 elif isinstance(v, bool):
227 lst.append("%s = %r" % (k, bool(v)))
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400228 else:
229 try:
Zack Williams045b63d2019-01-22 16:30:57 -0700230 lst.append("%s = %r" % (k, int(v)))
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400231 except ValueError:
Zack Williams045b63d2019-01-22 16:30:57 -0700232 lst.append("%s = %s" % (k, v))
233 validator_string = "validators=[%s]" % ", ".join(validator_lst)
234 option_string = ", ".join(lst)
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400235 if len(validator_lst) == 0:
236 return option_string
237 elif len(lst) == 0:
238 return validator_string
239 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700240 return option_string + ", " + validator_string
241
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400242
243def xproto_django_options_str(field, dport=None):
Scott Baker2e856be2019-02-07 09:28:09 -0800244 # This function is called for non-links (Strings, Ints, Booleans, ...)
245
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400246 output_dict = map_xproto_to_django(field)
247
Zack Williams045b63d2019-01-22 16:30:57 -0700248 if dport == "_":
249 dport = "+"
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400250
Zack Williams045b63d2019-01-22 16:30:57 -0700251 if dport and (dport == "+" or "+" not in dport):
252 output_dict["related_name"] = "%r" % dport
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400253
254 return format_options_string(output_dict)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400255
Zack Williams045b63d2019-01-22 16:30:57 -0700256
Sapan Bhatia1e021772017-08-19 02:15:48 -0400257def xproto_camel_to_underscore(name):
Zack Williams045b63d2019-01-22 16:30:57 -0700258 return re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
259
Sapan Bhatia1e021772017-08-19 02:15:48 -0400260
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400261def xproto_validations(options):
262 try:
Zack Williams045b63d2019-01-22 16:30:57 -0700263 return [
Zack Williams9a42f872019-02-15 17:56:04 -0700264 list(map(str.strip, validation.split(":")))
Zack Williams045b63d2019-01-22 16:30:57 -0700265 for validation in unquote(options["validators"]).split(",")
266 ]
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400267 except KeyError:
268 return []
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800269
Zack Williams045b63d2019-01-22 16:30:57 -0700270
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800271def xproto_optioned_fields_to_list(fields, option, val):
272 """
273 List all the field that have a particural option
274 :param fields: (list) an array of message fields
275 :param option: (string) the option to look for
276 :param val: (any) the value of the option
277 :return: list of strings, field names where option is set
278 """
279
280 optioned_fields = []
281 for f in fields:
282 option_names = []
Zack Williams9a42f872019-02-15 17:56:04 -0700283 for k, v in sorted(f["options"].items(), key=lambda t: t[0]): # sorted by key
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800284 option_names.append(k)
285
Zack Williams045b63d2019-01-22 16:30:57 -0700286 if option in option_names and f["options"][option] == val:
287 optioned_fields.append(f["name"])
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800288
289 return optioned_fields
290
Zack Williams045b63d2019-01-22 16:30:57 -0700291
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800292# TODO
293# - in modeldefs add info about this fields
294# - update the gui to have this fields as readonly