1
+ import re
1
2
import inflect
2
3
3
4
import frappe
@@ -13,15 +14,17 @@ def get_doctype_sdl(doctype, options):
13
14
ignore_custom_fields=False
14
15
)
15
16
"""
17
+ generated_enums = frappe ._dict ()
18
+
16
19
meta = frappe .get_meta (doctype )
17
- sdl , defined_fieldnames = get_basic_doctype_sdl (meta , options = options )
20
+ sdl , defined_fieldnames = get_basic_doctype_sdl (meta , options = options , generated_enums = generated_enums )
18
21
19
22
# Extend Doctype with Custom Fields
20
23
if not options .ignore_custom_fields and len (meta .get_custom_fields ()):
21
24
sdl += get_custom_field_sdl (meta , defined_fieldnames , options = options )
22
25
23
26
if not options .disable_enum_select_fields :
24
- sdl += get_select_docfield_enums (meta = meta , options = options )
27
+ sdl += get_select_docfield_enums (meta = meta , options = options , generated_enums = generated_enums )
25
28
26
29
# DocTypeSortingInput
27
30
if not meta .issingle :
@@ -34,7 +37,7 @@ def get_doctype_sdl(doctype, options):
34
37
return sdl
35
38
36
39
37
- def get_basic_doctype_sdl (meta : Meta , options : dict ):
40
+ def get_basic_doctype_sdl (meta : Meta , options : dict , generated_enums = None ):
38
41
dt = format_doctype (meta .name )
39
42
sdl = f"type { dt } implements BaseDocType {{"
40
43
@@ -62,7 +65,7 @@ def get_basic_doctype_sdl(meta: Meta, options: dict):
62
65
if cint (field .get ("is_custom_field" )):
63
66
continue
64
67
defined_fieldnames .append (field .fieldname )
65
- sdl += f"\n { get_field_sdl (meta , field , options = options )} "
68
+ sdl += f"\n { get_field_sdl (meta , field , options = options , generated_enums = generated_enums )} "
66
69
if field .fieldtype in ("Link" , "Dynamic Link" ):
67
70
sdl += f"\n { get_link_field_name_sdl (field )} "
68
71
@@ -87,14 +90,26 @@ def get_custom_field_sdl(meta, defined_fieldnames, options):
87
90
return sdl
88
91
89
92
90
- def get_select_docfield_enums (meta , options ):
93
+ def get_select_docfield_enums (meta , options , generated_enums = None ):
91
94
sdl = ""
92
95
for field in meta .get ("fields" , {"fieldtype" : "Select" }):
93
- if options .ignore_custom_fields and cint (field .get ("is_custom_field" )):
96
+
97
+ has_no_options = all ([len (x or "" ) == 0 for x in (field .options or "" ).split ("\n " )])
98
+
99
+ has_invalid_options = False
100
+ if any ([
101
+ contains_reserved_characters (option )
102
+ for option in (field .options or "" ).split ("\n " )
103
+ ]):
104
+ has_invalid_options = True
105
+
106
+ if (options .ignore_custom_fields and cint (field .get ("is_custom_field" ))) \
107
+ or has_no_options \
108
+ or has_invalid_options :
94
109
continue
95
110
96
111
sdl += "\n \n "
97
- sdl += f"enum { get_select_docfield_enum_name (meta .name , field )} {{"
112
+ sdl += f"enum { get_select_docfield_enum_name (meta .name , field , generated_enums )} {{"
98
113
for option in (field .get ("options" ) or "" ).split ("\n " ):
99
114
if not option or not len (option ):
100
115
continue
@@ -166,15 +181,15 @@ def get_query_type_extension(meta: Meta):
166
181
return sdl
167
182
168
183
169
- def get_field_sdl (meta , docfield , options : dict ):
170
- return f"{ docfield .fieldname } : { get_graphql_type (meta , docfield , options = options )} "
184
+ def get_field_sdl (meta , docfield , options : dict , generated_enums : list = None ):
185
+ return f"{ docfield .fieldname } : { get_graphql_type (meta , docfield , options = options , generated_enums = generated_enums )} "
171
186
172
187
173
188
def get_link_field_name_sdl (docfield ):
174
189
return f"{ docfield .fieldname } __name: String"
175
190
176
191
177
- def get_graphql_type (meta , docfield , options : dict ):
192
+ def get_graphql_type (meta , docfield , options : dict , generated_enums = None ):
178
193
string_fieldtypes = [
179
194
"Small Text" , "Long Text" , "Code" , "Text Editor" , "Markdown Editor" , "HTML Editor" ,
180
195
"Date" , "Datetime" , "Time" , "Text" , "Data" , "Rating" , "Read Only" ,
@@ -194,23 +209,29 @@ def get_graphql_type(meta, docfield, options: dict):
194
209
elif docfield .fieldtype in float_fieldtypes :
195
210
graphql_type = "Float"
196
211
elif docfield .fieldtype == "Link" :
197
- graphql_type = f"{ docfield .options . replace ( ' ' , '' )} "
212
+ graphql_type = f"{ format_doctype ( docfield .options )} "
198
213
elif docfield .fieldtype == "Dynamic Link" :
199
214
graphql_type = "BaseDocType"
200
215
elif docfield .fieldtype in table_fields :
201
- graphql_type = f"[{ docfield .options . replace ( ' ' , '' )} !]!"
216
+ graphql_type = f"[{ format_doctype ( docfield .options )} !]!"
202
217
elif docfield .fieldtype == "Password" :
203
218
graphql_type = "Password"
204
219
elif docfield .fieldtype == "Select" :
205
- graphql_type = get_select_docfield_enum_name (meta .name , docfield )
220
+ graphql_type = get_select_docfield_enum_name (meta .name , docfield , generated_enums )
206
221
207
222
# Mark NonNull if there is no empty option and is required
208
- has_empty_option = any (
209
- [len (x or "" ) == 0 for x in (docfield .options or "" ).split ("\n " )])
210
- if docfield .reqd and has_empty_option :
211
- frappe .throw (
212
- frappe ._ ("Please check your SELECT doc field on doctype {0}: {1}. The select field cannot be empty and required." ).format (docfield .parent , docfield .fieldname ))
213
- if docfield .reqd and not has_empty_option :
223
+ has_empty_option = all ([len (x or "" ) == 0 for x in (docfield .options or "" ).split ("\n " )])
224
+
225
+ has_invalid_options = False
226
+ if any ([
227
+ contains_reserved_characters (option )
228
+ for option in (docfield .options or "" ).split ("\n " )
229
+ ]):
230
+ has_invalid_options = True
231
+
232
+ if has_empty_option or has_invalid_options :
233
+ graphql_type = "String"
234
+ if docfield .reqd :
214
235
graphql_type += "!"
215
236
else :
216
237
frappe .throw (f"Invalid fieldtype: { docfield .fieldtype } " )
@@ -227,9 +248,39 @@ def get_plural(doctype):
227
248
228
249
229
250
def format_doctype (doctype ):
230
- return doctype .replace (" " , "" )
251
+ return remove_reserved_characters (doctype .replace (" " , "" ).replace ("-" , "_" ))
252
+
253
+
254
+ def get_select_docfield_enum_name (doctype , docfield , generated_enums = None ):
255
+
256
+ name = remove_reserved_characters (
257
+ f"{ doctype } { (docfield .label or docfield .fieldname ).title ()} SelectOptions"
258
+ .replace (" " , "" ))
231
259
260
+ if name in generated_enums .values ():
261
+ name = remove_reserved_characters (
262
+ f"{ doctype } { (docfield .fieldname ).title ()} SelectOptions"
263
+ .replace (" " , "" ))
232
264
233
- def get_select_docfield_enum_name (doctype , docfield ):
234
- return f"{ doctype } { (docfield .label or docfield .fieldname ).title ()} SelectOptions" .replace (
235
- " " , "" )
265
+ if generated_enums is not None :
266
+ if docfield in generated_enums :
267
+ name = generated_enums [docfield ]
268
+ else :
269
+ generated_enums [docfield ] = name
270
+
271
+ return name
272
+
273
+
274
+ def remove_reserved_characters (string ):
275
+ return re .sub (r"[^A-Za-z0-9_ ]" , "" , string )
276
+
277
+
278
+ def contains_reserved_characters (string ):
279
+ if not string :
280
+ return False
281
+
282
+ matches = re .match (r"^[A-Za-z_ ][A-Za-z0-9_ ]*$" , string )
283
+ if matches :
284
+ return False
285
+ else :
286
+ return True
0 commit comments