Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/apps/config.py: 74%

132 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import inspect 

2import os 

3import warnings 

4from importlib import import_module 

5 

6from django.core.exceptions import ImproperlyConfigured 

7from django.utils.deprecation import RemovedInDjango41Warning 

8from django.utils.functional import cached_property 

9from django.utils.module_loading import import_string, module_has_submodule 

10 

11APPS_MODULE_NAME = "apps" 

12MODELS_MODULE_NAME = "models" 

13 

14 

15class AppConfig: 

16 """Class representing a Django application and its configuration.""" 

17 

18 def __init__(self, app_name, app_module): 

19 # Full Python path to the application e.g. 'django.contrib.admin'. 

20 self.name = app_name 

21 

22 # Root module for the application e.g. <module 'django.contrib.admin' 

23 # from 'django/contrib/admin/__init__.py'>. 

24 self.module = app_module 

25 

26 # Reference to the Apps registry that holds this AppConfig. Set by the 

27 # registry when it registers the AppConfig instance. 

28 self.apps = None 

29 

30 # The following attributes could be defined at the class level in a 

31 # subclass, hence the test-and-set pattern. 

32 

33 # Last component of the Python path to the application e.g. 'admin'. 

34 # This value must be unique across a Django project. 

35 if not hasattr(self, "label"): 35 ↛ 37line 35 didn't jump to line 37, because the condition on line 35 was never false

36 self.label = app_name.rpartition(".")[2] 

37 if not self.label.isidentifier(): 37 ↛ 38line 37 didn't jump to line 38, because the condition on line 37 was never true

38 raise ImproperlyConfigured( 

39 "The app label '%s' is not a valid Python identifier." % self.label 

40 ) 

41 

42 # Human-readable name for the application e.g. "Admin". 

43 if not hasattr(self, "verbose_name"): 

44 self.verbose_name = self.label.title() 

45 

46 # Filesystem path to the application directory e.g. 

47 # '/path/to/django/contrib/admin'. 

48 if not hasattr(self, "path"): 48 ↛ 54line 48 didn't jump to line 54, because the condition on line 48 was never false

49 self.path = self._path_from_module(app_module) 

50 

51 # Module containing models e.g. <module 'django.contrib.admin.models' 

52 # from 'django/contrib/admin/models.py'>. Set by import_models(). 

53 # None if the application doesn't have a models module. 

54 self.models_module = None 

55 

56 # Mapping of lowercase model names to model classes. Initially set to 

57 # None to prevent accidental access before import_models() runs. 

58 self.models = None 

59 

60 def __repr__(self): 

61 return "<%s: %s>" % (self.__class__.__name__, self.label) 

62 

63 @cached_property 

64 def default_auto_field(self): 

65 from django.conf import settings 

66 

67 return settings.DEFAULT_AUTO_FIELD 

68 

69 @property 

70 def _is_default_auto_field_overridden(self): 

71 return self.__class__.default_auto_field is not AppConfig.default_auto_field 

72 

73 def _path_from_module(self, module): 

74 """Attempt to determine app's filesystem path from its module.""" 

75 # See #21874 for extended discussion of the behavior of this method in 

76 # various cases. 

77 # Convert to list because __path__ may not support indexing. 

78 paths = list(getattr(module, "__path__", [])) 

79 if len(paths) != 1: 79 ↛ 80line 79 didn't jump to line 80, because the condition on line 79 was never true

80 filename = getattr(module, "__file__", None) 

81 if filename is not None: 

82 paths = [os.path.dirname(filename)] 

83 else: 

84 # For unknown reasons, sometimes the list returned by __path__ 

85 # contains duplicates that must be removed (#25246). 

86 paths = list(set(paths)) 

87 if len(paths) > 1: 87 ↛ 88line 87 didn't jump to line 88, because the condition on line 87 was never true

88 raise ImproperlyConfigured( 

89 "The app module %r has multiple filesystem locations (%r); " 

90 "you must configure this app with an AppConfig subclass " 

91 "with a 'path' class attribute." % (module, paths) 

92 ) 

93 elif not paths: 93 ↛ 94line 93 didn't jump to line 94, because the condition on line 93 was never true

94 raise ImproperlyConfigured( 

95 "The app module %r has no filesystem location, " 

96 "you must configure this app with an AppConfig subclass " 

97 "with a 'path' class attribute." % module 

98 ) 

99 return paths[0] 

100 

101 @classmethod 

102 def create(cls, entry): 

103 """ 

104 Factory that creates an app config from an entry in INSTALLED_APPS. 

105 """ 

106 # create() eventually returns app_config_class(app_name, app_module). 

107 app_config_class = None 

108 app_config_name = None 

109 app_name = None 

110 app_module = None 

111 

112 # If import_module succeeds, entry points to the app module. 

113 try: 

114 app_module = import_module(entry) 

115 except Exception: 

116 pass 

117 else: 

118 # If app_module has an apps submodule that defines a single 

119 # AppConfig subclass, use it automatically. 

120 # To prevent this, an AppConfig subclass can declare a class 

121 # variable default = False. 

122 # If the apps module defines more than one AppConfig subclass, 

123 # the default one can declare default = True. 

124 if module_has_submodule(app_module, APPS_MODULE_NAME): 

125 mod_path = "%s.%s" % (entry, APPS_MODULE_NAME) 

126 mod = import_module(mod_path) 

127 # Check if there's exactly one AppConfig candidate, 

128 # excluding those that explicitly define default = False. 

129 app_configs = [ 

130 (name, candidate) 

131 for name, candidate in inspect.getmembers(mod, inspect.isclass) 

132 if ( 

133 issubclass(candidate, cls) 

134 and candidate is not cls 

135 and getattr(candidate, "default", True) 

136 ) 

137 ] 

138 if len(app_configs) == 1: 

139 app_config_class = app_configs[0][1] 

140 app_config_name = "%s.%s" % (mod_path, app_configs[0][0]) 

141 else: 

142 # Check if there's exactly one AppConfig subclass, 

143 # among those that explicitly define default = True. 

144 app_configs = [ 

145 (name, candidate) 

146 for name, candidate in app_configs 

147 if getattr(candidate, "default", False) 

148 ] 

149 if len(app_configs) > 1: 149 ↛ 150line 149 didn't jump to line 150, because the condition on line 149 was never true

150 candidates = [repr(name) for name, _ in app_configs] 

151 raise RuntimeError( 

152 "%r declares more than one default AppConfig: " 

153 "%s." % (mod_path, ", ".join(candidates)) 

154 ) 

155 elif len(app_configs) == 1: 155 ↛ 163line 155 didn't jump to line 163, because the condition on line 155 was never false

156 app_config_class = app_configs[0][1] 

157 app_config_name = "%s.%s" % (mod_path, app_configs[0][0]) 

158 

159 # If app_module specifies a default_app_config, follow the link. 

160 # default_app_config is deprecated, but still takes over the 

161 # automatic detection for backwards compatibility during the 

162 # deprecation period. 

163 try: 

164 new_entry = app_module.default_app_config 

165 except AttributeError: 

166 # Use the default app config class if we didn't find anything. 

167 if app_config_class is None: 

168 app_config_class = cls 

169 app_name = entry 

170 else: 

171 message = "%r defines default_app_config = %r. " % (entry, new_entry) 

172 if new_entry == app_config_name: 172 ↛ 178line 172 didn't jump to line 178, because the condition on line 172 was never false

173 message += ( 

174 "Django now detects this configuration automatically. " 

175 "You can remove default_app_config." 

176 ) 

177 else: 

178 message += ( 

179 "However, Django's automatic detection %s. You should " 

180 "move the default config class to the apps submodule " 

181 "of your application and, if this module defines " 

182 "several config classes, mark the default one with " 

183 "default = True." 

184 % ( 

185 "picked another configuration, %r" % app_config_name 

186 if app_config_name 

187 else "did not find this configuration" 

188 ) 

189 ) 

190 warnings.warn(message, RemovedInDjango41Warning, stacklevel=2) 

191 entry = new_entry 

192 app_config_class = None 

193 

194 # If import_string succeeds, entry is an app config class. 

195 if app_config_class is None: 

196 try: 

197 app_config_class = import_string(entry) 

198 except Exception: 

199 pass 

200 # If both import_module and import_string failed, it means that entry 

201 # doesn't have a valid value. 

202 if app_module is None and app_config_class is None: 202 ↛ 206line 202 didn't jump to line 206, because the condition on line 202 was never true

203 # If the last component of entry starts with an uppercase letter, 

204 # then it was likely intended to be an app config class; if not, 

205 # an app module. Provide a nice error message in both cases. 

206 mod_path, _, cls_name = entry.rpartition(".") 

207 if mod_path and cls_name[0].isupper(): 

208 # We could simply re-trigger the string import exception, but 

209 # we're going the extra mile and providing a better error 

210 # message for typos in INSTALLED_APPS. 

211 # This may raise ImportError, which is the best exception 

212 # possible if the module at mod_path cannot be imported. 

213 mod = import_module(mod_path) 

214 candidates = [ 

215 repr(name) 

216 for name, candidate in inspect.getmembers(mod, inspect.isclass) 

217 if issubclass(candidate, cls) and candidate is not cls 

218 ] 

219 msg = "Module '%s' does not contain a '%s' class." % ( 

220 mod_path, 

221 cls_name, 

222 ) 

223 if candidates: 

224 msg += " Choices are: %s." % ", ".join(candidates) 

225 raise ImportError(msg) 

226 else: 

227 # Re-trigger the module import exception. 

228 import_module(entry) 

229 

230 # Check for obvious errors. (This check prevents duck typing, but 

231 # it could be removed if it became a problem in practice.) 

232 if not issubclass(app_config_class, AppConfig): 232 ↛ 233line 232 didn't jump to line 233, because the condition on line 232 was never true

233 raise ImproperlyConfigured("'%s' isn't a subclass of AppConfig." % entry) 

234 

235 # Obtain app name here rather than in AppClass.__init__ to keep 

236 # all error checking for entries in INSTALLED_APPS in one place. 

237 if app_name is None: 

238 try: 

239 app_name = app_config_class.name 

240 except AttributeError: 

241 raise ImproperlyConfigured("'%s' must supply a name attribute." % entry) 

242 

243 # Ensure app_name points to a valid module. 

244 try: 

245 app_module = import_module(app_name) 

246 except ImportError: 

247 raise ImproperlyConfigured( 

248 "Cannot import '%s'. Check that '%s.%s.name' is correct." 

249 % ( 

250 app_name, 

251 app_config_class.__module__, 

252 app_config_class.__qualname__, 

253 ) 

254 ) 

255 

256 # Entry is a path to an app config class. 

257 return app_config_class(app_name, app_module) 

258 

259 def get_model(self, model_name, require_ready=True): 

260 """ 

261 Return the model with the given case-insensitive model_name. 

262 

263 Raise LookupError if no model exists with this name. 

264 """ 

265 if require_ready: 

266 self.apps.check_models_ready() 

267 else: 

268 self.apps.check_apps_ready() 

269 try: 

270 return self.models[model_name.lower()] 

271 except KeyError: 

272 raise LookupError( 

273 "App '%s' doesn't have a '%s' model." % (self.label, model_name) 

274 ) 

275 

276 def get_models(self, include_auto_created=False, include_swapped=False): 

277 """ 

278 Return an iterable of models. 

279 

280 By default, the following models aren't included: 

281 

282 - auto-created models for many-to-many relations without 

283 an explicit intermediate table, 

284 - models that have been swapped out. 

285 

286 Set the corresponding keyword argument to True to include such models. 

287 Keyword arguments aren't documented; they're a private API. 

288 """ 

289 self.apps.check_models_ready() 

290 for model in self.models.values(): 

291 if model._meta.auto_created and not include_auto_created: 

292 continue 

293 if model._meta.swapped and not include_swapped: 

294 continue 

295 yield model 

296 

297 def import_models(self): 

298 # Dictionary of models for this app, primarily maintained in the 

299 # 'all_models' attribute of the Apps this AppConfig is attached to. 

300 self.models = self.apps.all_models[self.label] 

301 

302 if module_has_submodule(self.module, MODELS_MODULE_NAME): 

303 models_module_name = "%s.%s" % (self.name, MODELS_MODULE_NAME) 

304 self.models_module = import_module(models_module_name) 

305 

306 def ready(self): 

307 """ 

308 Override this method in subclasses to run code when Django starts. 

309 """