Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/polymorphic/admin/childadmin.py: 30%

99 statements  

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

1""" 

2The child admin displays the change/delete view of the subclass model. 

3""" 

4import inspect 

5 

6from django.contrib import admin 

7from django.urls import resolve 

8from django.utils.translation import gettext_lazy as _ 

9 

10from polymorphic.utils import get_base_polymorphic_model 

11 

12from ..admin import PolymorphicParentModelAdmin 

13 

14 

15class ParentAdminNotRegistered(RuntimeError): 

16 "The admin site for the model is not registered." 

17 

18 

19class PolymorphicChildModelAdmin(admin.ModelAdmin): 

20 """ 

21 The *optional* base class for the admin interface of derived models. 

22 

23 This base class defines some convenience behavior for the admin interface: 

24 

25 * It corrects the breadcrumbs in the admin pages. 

26 * It adds the base model to the template lookup paths. 

27 * It allows to set ``base_form`` so the derived class will automatically include other fields in the form. 

28 * It allows to set ``base_fieldsets`` so the derived class will automatically display any extra fields. 

29 """ 

30 

31 #: The base model that the class uses (auto-detected if not set explicitly) 

32 base_model = None 

33 

34 #: By setting ``base_form`` instead of ``form``, any subclass fields are automatically added to the form. 

35 #: This is useful when your model admin class is inherited by others. 

36 base_form = None 

37 

38 #: By setting ``base_fieldsets`` instead of ``fieldsets``, 

39 #: any subclass fields can be automatically added. 

40 #: This is useful when your model admin class is inherited by others. 

41 base_fieldsets = None 

42 

43 #: Default title for extra fieldset 

44 extra_fieldset_title = _("Contents") 

45 

46 #: Whether the child admin model should be visible in the admin index page. 

47 show_in_index = False 

48 

49 def __init__(self, model, admin_site, *args, **kwargs): 

50 super().__init__(model, admin_site, *args, **kwargs) 

51 

52 if self.base_model is None: 52 ↛ 53line 52 didn't jump to line 53, because the condition on line 52 was never true

53 self.base_model = get_base_polymorphic_model(model) 

54 

55 def get_form(self, request, obj=None, **kwargs): 

56 # The django admin validation requires the form to have a 'class Meta: model = ..' 

57 # attribute, or it will complain that the fields are missing. 

58 # However, this enforces all derived ModelAdmin classes to redefine the model as well, 

59 # because they need to explicitly set the model again - it will stick with the base model. 

60 # 

61 # Instead, pass the form unchecked here, because the standard ModelForm will just work. 

62 # If the derived class sets the model explicitly, respect that setting. 

63 kwargs.setdefault("form", self.base_form or self.form) 

64 

65 # prevent infinite recursion when this is called from get_subclass_fields 

66 if not self.fieldsets and not self.fields: 

67 kwargs.setdefault("fields", "__all__") 

68 

69 return super().get_form(request, obj, **kwargs) 

70 

71 def get_model_perms(self, request): 

72 match = resolve(request.path_info) 

73 

74 if ( 

75 not self.show_in_index 

76 and match.app_name == "admin" 

77 and match.url_name in ("index", "app_list") 

78 ): 

79 return {"add": False, "change": False, "delete": False} 

80 return super().get_model_perms(request) 

81 

82 @property 

83 def change_form_template(self): 

84 opts = self.model._meta 

85 app_label = opts.app_label 

86 

87 # Pass the base options 

88 base_opts = self.base_model._meta 

89 base_app_label = base_opts.app_label 

90 

91 return [ 

92 f"admin/{app_label}/{opts.object_name.lower()}/change_form.html", 

93 "admin/%s/change_form.html" % app_label, 

94 # Added: 

95 "admin/%s/%s/change_form.html" % (base_app_label, base_opts.object_name.lower()), 

96 "admin/%s/change_form.html" % base_app_label, 

97 "admin/polymorphic/change_form.html", 

98 "admin/change_form.html", 

99 ] 

100 

101 @property 

102 def delete_confirmation_template(self): 

103 opts = self.model._meta 

104 app_label = opts.app_label 

105 

106 # Pass the base options 

107 base_opts = self.base_model._meta 

108 base_app_label = base_opts.app_label 

109 

110 return [ 

111 "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()), 

112 "admin/%s/delete_confirmation.html" % app_label, 

113 # Added: 

114 "admin/%s/%s/delete_confirmation.html" 

115 % (base_app_label, base_opts.object_name.lower()), 

116 "admin/%s/delete_confirmation.html" % base_app_label, 

117 "admin/polymorphic/delete_confirmation.html", 

118 "admin/delete_confirmation.html", 

119 ] 

120 

121 @property 

122 def object_history_template(self): 

123 opts = self.model._meta 

124 app_label = opts.app_label 

125 

126 # Pass the base options 

127 base_opts = self.base_model._meta 

128 base_app_label = base_opts.app_label 

129 

130 return [ 

131 f"admin/{app_label}/{opts.object_name.lower()}/object_history.html", 

132 "admin/%s/object_history.html" % app_label, 

133 # Added: 

134 "admin/%s/%s/object_history.html" % (base_app_label, base_opts.object_name.lower()), 

135 "admin/%s/object_history.html" % base_app_label, 

136 "admin/polymorphic/object_history.html", 

137 "admin/object_history.html", 

138 ] 

139 

140 def _get_parent_admin(self): 

141 # this returns parent admin instance on which to call response_post_save methods 

142 parent_model = self.model._meta.get_field("polymorphic_ctype").model 

143 if parent_model == self.model: 

144 # when parent_model is in among child_models, just return super instance 

145 return super() 

146 

147 try: 

148 return self.admin_site._registry[parent_model] 

149 except KeyError: 

150 # Admin is not registered for polymorphic_ctype model, but perhaps it's registered 

151 # for a intermediate proxy model, between the parent_model and this model. 

152 for klass in inspect.getmro(self.model): 

153 if not issubclass(klass, parent_model): 

154 continue # e.g. found a mixin. 

155 

156 # Fetch admin instance for model class, see if it's a possible candidate. 

157 model_admin = self.admin_site._registry.get(klass) 

158 if model_admin is not None and isinstance( 

159 model_admin, PolymorphicParentModelAdmin 

160 ): 

161 return model_admin # Success! 

162 

163 # If we get this far without returning there is no admin available 

164 raise ParentAdminNotRegistered( 

165 f"No parent admin was registered for a '{parent_model}' model." 

166 ) 

167 

168 def response_post_save_add(self, request, obj): 

169 return self._get_parent_admin().response_post_save_add(request, obj) 

170 

171 def response_post_save_change(self, request, obj): 

172 return self._get_parent_admin().response_post_save_change(request, obj) 

173 

174 def render_change_form(self, request, context, add=False, change=False, form_url="", obj=None): 

175 context.update({"base_opts": self.base_model._meta}) 

176 return super().render_change_form( 

177 request, context, add=add, change=change, form_url=form_url, obj=obj 

178 ) 

179 

180 def delete_view(self, request, object_id, context=None): 

181 extra_context = {"base_opts": self.base_model._meta} 

182 return super().delete_view(request, object_id, extra_context) 

183 

184 def history_view(self, request, object_id, extra_context=None): 

185 # Make sure the history view can also display polymorphic breadcrumbs 

186 context = {"base_opts": self.base_model._meta} 

187 if extra_context: 

188 context.update(extra_context) 

189 return super().history_view(request, object_id, extra_context=context) 

190 

191 # ---- Extra: improving the form/fieldset default display ---- 

192 

193 def get_base_fieldsets(self, request, obj=None): 

194 return self.base_fieldsets 

195 

196 def get_fieldsets(self, request, obj=None): 

197 base_fieldsets = self.get_base_fieldsets(request, obj) 

198 

199 # If subclass declares fieldsets or fields, this is respected 

200 if self.fieldsets or self.fields or not self.base_fieldsets: 

201 return super().get_fieldsets(request, obj) 

202 

203 # Have a reasonable default fieldsets, 

204 # where the subclass fields are automatically included. 

205 other_fields = self.get_subclass_fields(request, obj) 

206 

207 if other_fields: 

208 return ( 

209 base_fieldsets[0], 

210 (self.extra_fieldset_title, {"fields": other_fields}), 

211 ) + base_fieldsets[1:] 

212 else: 

213 return base_fieldsets 

214 

215 def get_subclass_fields(self, request, obj=None): 

216 # Find out how many fields would really be on the form, 

217 # if it weren't restricted by declared fields. 

218 exclude = list(self.exclude or []) 

219 exclude.extend(self.get_readonly_fields(request, obj)) 

220 

221 # By not declaring the fields/form in the base class, 

222 # get_form() will populate the form with all available fields. 

223 form = self.get_form(request, obj, exclude=exclude) 

224 subclass_fields = list(form.base_fields.keys()) + list( 

225 self.get_readonly_fields(request, obj) 

226 ) 

227 

228 # Find which fields are not part of the common fields. 

229 for fieldset in self.get_base_fieldsets(request, obj): 

230 for field in fieldset[1]["fields"]: 

231 try: 

232 subclass_fields.remove(field) 

233 except ValueError: 

234 pass # field not found in form, Django will raise exception later. 

235 return subclass_fields