blob: 613462c46d7ddd084a5ec929df2f05c1d4dc3248 [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
Scott Baker08d10402019-04-08 16:19:59 -070016from .base import unquote, xproto_string_type
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):
Scott Baker08d10402019-04-08 16:19:59 -070044 # xproto_string_type will return "string" if the options.text=False or "text" if options.text=True
45 xtype = xproto_string_type(xptags)
46 if xtype == "string":
47 if "content_type" in xptags:
48 return django_content_type_string(xptags)
49 else:
50 # TODO(smbaker): This is a workaround for incorrect xproto in many services. Prior behavior was to
51 # toggle between Charfield and Textfield when max_length was unspecified, rather than to require
52 # max_length to be specified. Remove this workaround as soon as services have been migrated.
53 if "max_length" in xptags:
54 return "CharField"
55 else:
56 return "TextField"
57 elif xtype == "text":
Zack Williams045b63d2019-01-22 16:30:57 -070058 return "TextField"
Scott Baker08d10402019-04-08 16:19:59 -070059 else:
60 raise Exception("Unknown xproto_string type %s" % xtype, xptags=xptags)
Zack Williams045b63d2019-01-22 16:30:57 -070061
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040062
63def xproto_django_type(xptype, xptags):
Zack Williams045b63d2019-01-22 16:30:57 -070064 if xptype == "string":
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040065 return django_string_type(xptags)
Zack Williams045b63d2019-01-22 16:30:57 -070066 elif xptype == "float":
67 return "FloatField"
68 elif xptype == "bool":
69 return "BooleanField"
70 elif xptype == "uint32":
71 return "IntegerField"
72 elif xptype == "int32":
73 return "IntegerField"
74 elif xptype == "int64":
75 return "BigIntegerField"
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040076 else:
Zack Williams045b63d2019-01-22 16:30:57 -070077 raise Exception("Unknown Type: %s" % xptype)
78
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040079
80def xproto_django_link_type(f):
Zack Williams045b63d2019-01-22 16:30:57 -070081 if f["link_type"] == "manytoone":
82 return "ForeignKey"
83 elif f["link_type"] == "onetoone":
84 return "OneToOneField"
85 elif f["link_type"] == "manytomany":
86 if f["dst_port"]:
87 return "ManyToManyField"
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040088 else:
Zack Williams045b63d2019-01-22 16:30:57 -070089 return "GenericRelation"
90
Sapan Bhatiad8e4a232017-07-12 21:20:06 -040091
92def map_xproto_to_django(f):
Zack Williams00e22d62019-03-01 22:32:13 -070093
Zack Williams045b63d2019-01-22 16:30:57 -070094 allowed_keys = [
Zack Williams00e22d62019-03-01 22:32:13 -070095 "auto_now_add",
Zack Williams045b63d2019-01-22 16:30:57 -070096 "blank",
97 "choices",
98 "db_index",
Zack Williams00e22d62019-03-01 22:32:13 -070099 "default",
Zack Williams045b63d2019-01-22 16:30:57 -0700100 "editable",
Zack Williams00e22d62019-03-01 22:32:13 -0700101 "help_text",
102 "max_length",
Zack Williams045b63d2019-01-22 16:30:57 -0700103 "max_value",
Zack Williams00e22d62019-03-01 22:32:13 -0700104 "min_value",
105 "null",
106 "on_delete",
107 "unique",
108 "verbose_name",
Zack Williams045b63d2019-01-22 16:30:57 -0700109 ]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400110
Zack Williams00e22d62019-03-01 22:32:13 -0700111 out = {} # output dictionary
Scott Baker2e856be2019-02-07 09:28:09 -0800112
Zack Williams00e22d62019-03-01 22:32:13 -0700113 # filter options dict to only have allowed keys
Zack Williams045b63d2019-01-22 16:30:57 -0700114 for k, v in f["options"].items():
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400115 if k in allowed_keys:
Zack Williams00e22d62019-03-01 22:32:13 -0700116 out[k] = v
117
118 # deal with optional/required modifier fields, and manytomany links
119 # modifier is not added to "out" dict, but affects blank/null truth
120 modifier = f["options"].get('modifier')
121 link_type = f.get("link_type")
122
123 # in some tests, there is no field type
124 if "type" in f:
125 field_type = f["type"]
126 else:
127 field_type = None
128
129 mod_out = {}
130
131 if modifier == "required":
132
Scott Baker7ae3a8f2019-03-05 16:24:14 -0800133 mod_out["blank"] = 'False'
Zack Williams00e22d62019-03-01 22:32:13 -0700134
135 if link_type != "manytomany":
136 mod_out["null"] = 'False'
137
138 elif modifier == "optional":
139
140 mod_out["blank"] = 'True'
141
142 # set defaults on link types
143 if link_type != "manytomany" and field_type != "bool":
144 mod_out["null"] = 'True'
145
146 else:
147 print("map_xproto_to_django - unknown modifier type: %s on %s" % (modifier, f), file=sys.stderr)
148
149 # print an error if there's a field conflict
150 for kmo in mod_out.keys():
151 if kmo in out:
152 if out[kmo] != mod_out[kmo]:
153 print("Option '%s' is manually set to value '%s', which "
154 "conflicts with value '%s' set automatically by modifier on field: %s" %
155 (kmo, out[kmo], mod_out[kmo], f), file=sys.stderr)
156
157 out.update(mod_out) # overwrite out keys with mod_out
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400158
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400159 return out
160
Zack Williams045b63d2019-01-22 16:30:57 -0700161
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400162def xproto_django_link_options_str(field, dport=None):
Scott Baker2e856be2019-02-07 09:28:09 -0800163 # Note that this function is called for links (ForeignKeys, M2Ms)
164
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400165 output_dict = map_xproto_to_django(field)
166
Zack Williams045b63d2019-01-22 16:30:57 -0700167 if dport and (dport == "+" or "+" not in dport):
168 output_dict["related_name"] = "%r" % dport
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400169
170 try:
Zack Williams045b63d2019-01-22 16:30:57 -0700171 if field["through"]:
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400172 d = {}
Zack Williams045b63d2019-01-22 16:30:57 -0700173 if isinstance(field["through"], str):
174 split = field["through"].rsplit(".", 1)
175 d["name"] = split[-1]
176 if len(split) == 2:
177 d["package"] = split[0]
178 d["fqn"] = "package" + "." + d["name"]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400179 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700180 d["fqn"] = d["name"]
181 d["package"] = ""
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400182 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700183 d = field["through"]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400184
Zack Williams045b63d2019-01-22 16:30:57 -0700185 if not d["name"].endswith("_" + field["name"]):
186 output_dict["through"] = "%r" % d["fqn"]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400187 except KeyError:
188 pass
189
190 return format_options_string(output_dict)
191
Zack Williams045b63d2019-01-22 16:30:57 -0700192
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400193def use_native_django_validators(k, v):
194
195 validators_map = {
Zack Williams045b63d2019-01-22 16:30:57 -0700196 "min_value": "MinValueValidator",
197 "max_value": "MaxValueValidator",
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400198 }
199
200 return "%s(%s)" % (validators_map[k], v)
201
Zack Williams045b63d2019-01-22 16:30:57 -0700202
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400203def format_options_string(d):
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400204
Zack Williams045b63d2019-01-22 16:30:57 -0700205 known_validators = ["min_value", "max_value"]
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400206 validator_lst = []
207
Zack Williams045b63d2019-01-22 16:30:57 -0700208 if not d:
209 return ""
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400210 else:
211
212 lst = []
Zack Williams9a42f872019-02-15 17:56:04 -0700213 for k, v in sorted(d.items(), key=lambda t: t[0]): # sorted by key
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400214 if k in known_validators:
215 validator_lst.append(use_native_django_validators(k, v))
Zack Williams045b63d2019-01-22 16:30:57 -0700216 elif isinstance(v, str) and k == "default" and v.endswith('()"'):
217 lst.append("%s = %s" % (k, v[1:-3]))
218 elif isinstance(v, str) and v.startswith('"'):
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400219 try:
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400220 # unquote the value if necessary
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400221 tup = eval(v[1:-1])
Zack Williams045b63d2019-01-22 16:30:57 -0700222 if isinstance(tup, tuple):
223 lst.append("%s = %r" % (k, tup))
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400224 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700225 lst.append("%s = %s" % (k, v))
226 except BaseException:
227 lst.append("%s = %s" % (k, v))
228 elif isinstance(v, bool):
229 lst.append("%s = %r" % (k, bool(v)))
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400230 else:
231 try:
Zack Williams045b63d2019-01-22 16:30:57 -0700232 lst.append("%s = %r" % (k, int(v)))
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400233 except ValueError:
Zack Williams045b63d2019-01-22 16:30:57 -0700234 lst.append("%s = %s" % (k, v))
235 validator_string = "validators=[%s]" % ", ".join(validator_lst)
236 option_string = ", ".join(lst)
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400237 if len(validator_lst) == 0:
238 return option_string
239 elif len(lst) == 0:
240 return validator_string
241 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700242 return option_string + ", " + validator_string
243
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400244
245def xproto_django_options_str(field, dport=None):
Scott Baker2e856be2019-02-07 09:28:09 -0800246 # This function is called for non-links (Strings, Ints, Booleans, ...)
247
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400248 output_dict = map_xproto_to_django(field)
249
Zack Williams045b63d2019-01-22 16:30:57 -0700250 if dport == "_":
251 dport = "+"
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400252
Zack Williams045b63d2019-01-22 16:30:57 -0700253 if dport and (dport == "+" or "+" not in dport):
254 output_dict["related_name"] = "%r" % dport
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400255
256 return format_options_string(output_dict)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400257
Zack Williams045b63d2019-01-22 16:30:57 -0700258
Sapan Bhatia1e021772017-08-19 02:15:48 -0400259def xproto_camel_to_underscore(name):
Zack Williams045b63d2019-01-22 16:30:57 -0700260 return re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
261
Sapan Bhatia1e021772017-08-19 02:15:48 -0400262
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400263def xproto_validations(options):
264 try:
Zack Williams045b63d2019-01-22 16:30:57 -0700265 return [
Zack Williams9a42f872019-02-15 17:56:04 -0700266 list(map(str.strip, validation.split(":")))
Zack Williams045b63d2019-01-22 16:30:57 -0700267 for validation in unquote(options["validators"]).split(",")
268 ]
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400269 except KeyError:
270 return []
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800271
Zack Williams045b63d2019-01-22 16:30:57 -0700272
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800273def xproto_optioned_fields_to_list(fields, option, val):
274 """
275 List all the field that have a particural option
276 :param fields: (list) an array of message fields
277 :param option: (string) the option to look for
278 :param val: (any) the value of the option
279 :return: list of strings, field names where option is set
280 """
281
282 optioned_fields = []
283 for f in fields:
284 option_names = []
Zack Williams9a42f872019-02-15 17:56:04 -0700285 for k, v in sorted(f["options"].items(), key=lambda t: t[0]): # sorted by key
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800286 option_names.append(k)
287
Zack Williams045b63d2019-01-22 16:30:57 -0700288 if option in option_names and f["options"][option] == val:
289 optioned_fields.append(f["name"])
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800290
291 return optioned_fields
292
Zack Williams045b63d2019-01-22 16:30:57 -0700293
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800294# TODO
295# - in modeldefs add info about this fields
296# - update the gui to have this fields as readonly