blob: 6fac823c0291b2a7134aaf0634c4d17b71f3f54c [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
Scott Baker7ae3a8f2019-03-05 16:24:14 -0800127 mod_out["blank"] = 'False'
Zack Williams00e22d62019-03-01 22:32:13 -0700128
129 if link_type != "manytomany":
130 mod_out["null"] = 'False'
131
132 elif modifier == "optional":
133
134 mod_out["blank"] = 'True'
135
136 # set defaults on link types
137 if link_type != "manytomany" and field_type != "bool":
138 mod_out["null"] = 'True'
139
140 else:
141 print("map_xproto_to_django - unknown modifier type: %s on %s" % (modifier, f), file=sys.stderr)
142
143 # print an error if there's a field conflict
144 for kmo in mod_out.keys():
145 if kmo in out:
146 if out[kmo] != mod_out[kmo]:
147 print("Option '%s' is manually set to value '%s', which "
148 "conflicts with value '%s' set automatically by modifier on field: %s" %
149 (kmo, out[kmo], mod_out[kmo], f), file=sys.stderr)
150
151 out.update(mod_out) # overwrite out keys with mod_out
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400152
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400153 return out
154
Zack Williams045b63d2019-01-22 16:30:57 -0700155
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400156def xproto_django_link_options_str(field, dport=None):
Scott Baker2e856be2019-02-07 09:28:09 -0800157 # Note that this function is called for links (ForeignKeys, M2Ms)
158
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400159 output_dict = map_xproto_to_django(field)
160
Zack Williams045b63d2019-01-22 16:30:57 -0700161 if dport and (dport == "+" or "+" not in dport):
162 output_dict["related_name"] = "%r" % dport
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400163
164 try:
Zack Williams045b63d2019-01-22 16:30:57 -0700165 if field["through"]:
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400166 d = {}
Zack Williams045b63d2019-01-22 16:30:57 -0700167 if isinstance(field["through"], str):
168 split = field["through"].rsplit(".", 1)
169 d["name"] = split[-1]
170 if len(split) == 2:
171 d["package"] = split[0]
172 d["fqn"] = "package" + "." + d["name"]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400173 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700174 d["fqn"] = d["name"]
175 d["package"] = ""
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400176 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700177 d = field["through"]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400178
Zack Williams045b63d2019-01-22 16:30:57 -0700179 if not d["name"].endswith("_" + field["name"]):
180 output_dict["through"] = "%r" % d["fqn"]
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400181 except KeyError:
182 pass
183
184 return format_options_string(output_dict)
185
Zack Williams045b63d2019-01-22 16:30:57 -0700186
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400187def use_native_django_validators(k, v):
188
189 validators_map = {
Zack Williams045b63d2019-01-22 16:30:57 -0700190 "min_value": "MinValueValidator",
191 "max_value": "MaxValueValidator",
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400192 }
193
194 return "%s(%s)" % (validators_map[k], v)
195
Zack Williams045b63d2019-01-22 16:30:57 -0700196
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400197def format_options_string(d):
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400198
Zack Williams045b63d2019-01-22 16:30:57 -0700199 known_validators = ["min_value", "max_value"]
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400200 validator_lst = []
201
Zack Williams045b63d2019-01-22 16:30:57 -0700202 if not d:
203 return ""
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400204 else:
205
206 lst = []
Zack Williams9a42f872019-02-15 17:56:04 -0700207 for k, v in sorted(d.items(), key=lambda t: t[0]): # sorted by key
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400208 if k in known_validators:
209 validator_lst.append(use_native_django_validators(k, v))
Zack Williams045b63d2019-01-22 16:30:57 -0700210 elif isinstance(v, str) and k == "default" and v.endswith('()"'):
211 lst.append("%s = %s" % (k, v[1:-3]))
212 elif isinstance(v, str) and v.startswith('"'):
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400213 try:
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400214 # unquote the value if necessary
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400215 tup = eval(v[1:-1])
Zack Williams045b63d2019-01-22 16:30:57 -0700216 if isinstance(tup, tuple):
217 lst.append("%s = %r" % (k, tup))
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400218 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700219 lst.append("%s = %s" % (k, v))
220 except BaseException:
221 lst.append("%s = %s" % (k, v))
222 elif isinstance(v, bool):
223 lst.append("%s = %r" % (k, bool(v)))
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400224 else:
225 try:
Zack Williams045b63d2019-01-22 16:30:57 -0700226 lst.append("%s = %r" % (k, int(v)))
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400227 except ValueError:
Zack Williams045b63d2019-01-22 16:30:57 -0700228 lst.append("%s = %s" % (k, v))
229 validator_string = "validators=[%s]" % ", ".join(validator_lst)
230 option_string = ", ".join(lst)
Matteo Scandolo61a9f202018-08-01 08:58:13 -0400231 if len(validator_lst) == 0:
232 return option_string
233 elif len(lst) == 0:
234 return validator_string
235 else:
Zack Williams045b63d2019-01-22 16:30:57 -0700236 return option_string + ", " + validator_string
237
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400238
239def xproto_django_options_str(field, dport=None):
Scott Baker2e856be2019-02-07 09:28:09 -0800240 # This function is called for non-links (Strings, Ints, Booleans, ...)
241
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400242 output_dict = map_xproto_to_django(field)
243
Zack Williams045b63d2019-01-22 16:30:57 -0700244 if dport == "_":
245 dport = "+"
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400246
Zack Williams045b63d2019-01-22 16:30:57 -0700247 if dport and (dport == "+" or "+" not in dport):
248 output_dict["related_name"] = "%r" % dport
Sapan Bhatiad8e4a232017-07-12 21:20:06 -0400249
250 return format_options_string(output_dict)
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400251
Zack Williams045b63d2019-01-22 16:30:57 -0700252
Sapan Bhatia1e021772017-08-19 02:15:48 -0400253def xproto_camel_to_underscore(name):
Zack Williams045b63d2019-01-22 16:30:57 -0700254 return re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name)
255
Sapan Bhatia1e021772017-08-19 02:15:48 -0400256
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400257def xproto_validations(options):
258 try:
Zack Williams045b63d2019-01-22 16:30:57 -0700259 return [
Zack Williams9a42f872019-02-15 17:56:04 -0700260 list(map(str.strip, validation.split(":")))
Zack Williams045b63d2019-01-22 16:30:57 -0700261 for validation in unquote(options["validators"]).split(",")
262 ]
Sapan Bhatia5ea307d2017-07-19 00:13:21 -0400263 except KeyError:
264 return []
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800265
Zack Williams045b63d2019-01-22 16:30:57 -0700266
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800267def xproto_optioned_fields_to_list(fields, option, val):
268 """
269 List all the field that have a particural option
270 :param fields: (list) an array of message fields
271 :param option: (string) the option to look for
272 :param val: (any) the value of the option
273 :return: list of strings, field names where option is set
274 """
275
276 optioned_fields = []
277 for f in fields:
278 option_names = []
Zack Williams9a42f872019-02-15 17:56:04 -0700279 for k, v in sorted(f["options"].items(), key=lambda t: t[0]): # sorted by key
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800280 option_names.append(k)
281
Zack Williams045b63d2019-01-22 16:30:57 -0700282 if option in option_names and f["options"][option] == val:
283 optioned_fields.append(f["name"])
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800284
285 return optioned_fields
286
Zack Williams045b63d2019-01-22 16:30:57 -0700287
Matteo Scandolo23cf15f2018-03-06 18:12:36 -0800288# TODO
289# - in modeldefs add info about this fields
290# - update the gui to have this fields as readonly