Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/modules/moduleconf.py: 0%

80 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-16 22:16 +0000

1import logging 

2from viur.core import Module, conf, db, current, i18n, tasks, skeleton 

3from viur.core.bones import StringBone, TextBone, SelectBone, TreeLeafBone 

4from viur.core.bones.text import _defaultTags 

5from viur.core.prototypes import List 

6 

7 

8MODULECONF_KINDNAME = "viur-module-conf" 

9 

10 

11class ModuleConfScriptSkel(skeleton.RelSkel): 

12 

13 name = StringBone( 

14 descr="Label", 

15 required=True, 

16 params={ 

17 "tooltip": "Label for the action button displayed." 

18 }, 

19 ) 

20 

21 icon = StringBone( 

22 descr="Icon", 

23 params={ 

24 "tooltip": "Shoelace-conforming icon identifier." 

25 }, 

26 ) 

27 

28 capable = SelectBone( 

29 descr="Arguments", 

30 required=True, 

31 defaultValue="none", 

32 values={ 

33 "none": "none: No arguments, always executable", 

34 "single": "single: Run script with single entry key as argument", 

35 "multiple": "multiple: Run script with list of entity keys as argument", 

36 }, 

37 params={ 

38 "tooltip": 

39 "Describes the behavior in the admin, " 

40 "if and how selected entries from the module are being processed." 

41 }, 

42 ) 

43 

44 access = SelectBone( 

45 descr="Required access rights", 

46 values=lambda: { 

47 right: i18n.translate(f"server.modules.user.accessright.{right}", defaultText=right) 

48 for right in sorted(conf.user.access_rights) 

49 }, 

50 multiple=True, 

51 params={ 

52 "tooltip": 

53 "To whom the button should be displayed in the admin. " 

54 "In addition, the admin checks whether all rights of the script are also fulfilled.", 

55 }, 

56 ) 

57 

58 

59class ModuleConfSkel(skeleton.Skeleton): 

60 kindName = MODULECONF_KINDNAME 

61 

62 _valid_tags = ['b', 'a', 'i', 'u', 'span', 'div', 'p', 'ol', 'ul', 'li', 'abbr', 'sub', 'sup', 'h1', 'h2', 'h3', 

63 'h4', 'h5', 'h6', 'br', 'hr', 'strong', 'blockquote', 'em'] 

64 _valid_html = _defaultTags.copy() 

65 _valid_html["validTags"] = _valid_tags 

66 

67 name = StringBone( 

68 descr=i18n.translate("modulename"), 

69 readOnly=True, 

70 ) 

71 

72 help_text = TextBone( 

73 descr=i18n.translate("module helptext"), 

74 validHtml=_valid_html, 

75 ) 

76 

77 help_text_add = TextBone( 

78 descr=i18n.translate("add helptext"), 

79 validHtml=_valid_html, 

80 ) 

81 

82 help_text_edit = TextBone( 

83 descr=i18n.translate("edit helptext"), 

84 validHtml=_valid_html, 

85 ) 

86 

87 scripts = TreeLeafBone( 

88 descr=i18n.translate("scriptor scripts"), 

89 module="script", 

90 kind="viur-script-leaf", 

91 using=ModuleConfScriptSkel, 

92 refKeys=[ 

93 "key", 

94 "name", 

95 "access", 

96 ], 

97 multiple=True, 

98 ) 

99 

100 

101class ModuleConf(List): 

102 """ 

103 This module is for ViUR internal purposes only. 

104 It lists all other modules to be able to provide them with help texts. 

105 """ 

106 MODULES = set() # will be filled by read_all_modules 

107 kindName = MODULECONF_KINDNAME 

108 accessRights = ["edit"] 

109 default_order = None # disable default ordering for ModuleConf 

110 

111 def adminInfo(self): 

112 return conf.moduleconf_admin_info or {} 

113 

114 def canAdd(self): 

115 return False 

116 

117 def canDelete(self, skel): 

118 return False 

119 

120 def canEdit(self, skel): 

121 if super().canEdit(skel): 

122 return True 

123 

124 # Check for "manage"-flag on current user 

125 return (cuser := current.user.get()) and f"""{skel["name"]}-manage""" in cuser["access"] 

126 

127 def listFilter(self, query): 

128 original_query = query 

129 

130 # when super-call does not satisfy... 

131 if not (query := super().listFilter(query)): 

132 if cuser := current.user.get(): 

133 # ... then, list modules the user is allowed to use! 

134 user_modules = set(right.split("-", 1)[0] for right in cuser["access"] if "-" in right) 

135 

136 query = original_query 

137 query.filter("name IN", tuple(user_modules)) 

138 

139 return query 

140 

141 @classmethod 

142 def get_by_module_name(cls, module_name: str) -> None | skeleton.SkeletonInstance: 

143 db_key = db.Key(MODULECONF_KINDNAME, module_name) 

144 skel = conf.main_app.vi._moduleconf.viewSkel() 

145 if not skel.fromDB(db_key): 

146 logging.error(f"module({module_name}) not found") 

147 return None 

148 

149 return skel 

150 

151 @tasks.StartupTask 

152 @staticmethod 

153 def read_all_modules(): 

154 db_module_names = (m["name"] for m in db.Query(MODULECONF_KINDNAME).run(999)) 

155 visited_modules = set() 

156 

157 def collect_modules(parent, depth: int = 0, prefix: str = "") -> None: 

158 """Recursively collects all routable modules for the vi renderer""" 

159 if depth > 10: 

160 logging.warning(f"Reached maximum recursion limit of {depth} at {parent=}") 

161 return 

162 

163 for module_name in dir(parent): 

164 module = getattr(parent, module_name, None) 

165 if not isinstance(module, Module): 

166 continue 

167 if module in visited_modules: 

168 # Some modules reference other modules as parents, this will 

169 # lead to infinite recursion. We can avoid reaching the 

170 # maximum recursion limit by remembering already seen modules. 

171 if conf.debug.trace: 

172 logging.debug(f"Already visited and added {module=}") 

173 continue 

174 module_name = f"{prefix}{module_name}" 

175 visited_modules.add(module) 

176 ModuleConf.MODULES.add(module_name) 

177 if module_name not in db_module_names: 

178 skel = conf.main_app.vi._moduleconf.addSkel() 

179 skel["key"] = db.Key(MODULECONF_KINDNAME, module_name) 

180 skel["name"] = module_name 

181 skel.toDB() 

182 

183 # Collect children 

184 collect_modules(module, depth=depth + 1, prefix=f"{module_name}.") 

185 

186 collect_modules(conf.main_app.vi) 

187 # TODO: Remove entries from MODULECONF_KINDNAME which are in db_module_names but not in ModuleConf.MODULES 

188 

189 

190ModuleConf.json = True