Coverage for /Users/davegaeddert/Development/dropseed/plain/plain/plain/views/objects.py: 34%

107 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-10-16 22:04 -0500

1from plain.exceptions import ImproperlyConfigured, ObjectDoesNotExist 

2from plain.http import Http404, Response, ResponseRedirect 

3 

4from .forms import FormView 

5from .templates import TemplateView 

6 

7 

8class ObjectTemplateViewMixin: 

9 context_object_name = "" 

10 

11 def get(self) -> Response: 

12 self.load_object() 

13 return self.render_template() 

14 

15 def load_object(self) -> None: 

16 try: 

17 self.object = self.get_object() 

18 except ObjectDoesNotExist: 

19 raise Http404 

20 

21 if not self.object: 

22 # Also raise 404 if the object is None 

23 raise Http404 

24 

25 def get_object(self): # Intentionally untyped... subclasses must override this. 

26 raise NotImplementedError( 

27 f"get_object() is not implemented on {self.__class__.__name__}" 

28 ) 

29 

30 def get_template_context(self) -> dict: 

31 """Insert the single object into the context dict.""" 

32 context = super().get_template_context() # type: ignore 

33 context["object"] = self.object 

34 if self.context_object_name: 

35 context[self.context_object_name] = self.object 

36 elif hasattr(self.object, "_meta"): 

37 context[self.object._meta.model_name] = self.object 

38 return context 

39 

40 def get_template_names(self) -> list[str]: 

41 """ 

42 Return a list of template names to be used for the request. May not be 

43 called if render_to_response() is overridden. Return the following list: 

44 

45 * the value of ``template_name`` on the view (if provided) 

46 object instance that the view is operating upon (if available) 

47 * ``<package_label>/<model_name><template_name_suffix>.html`` 

48 """ 

49 if self.template_name: # type: ignore 

50 return [self.template_name] # type: ignore 

51 

52 # If template_name isn't specified, it's not a problem -- 

53 # we just start with an empty list. 

54 names = [] 

55 

56 # The least-specific option is the default <app>/<model>_detail.html; 

57 # only use this if the object in question is a model. 

58 if hasattr(self.object, "_meta"): 

59 object_meta = self.object._meta 

60 names.append( 

61 f"{object_meta.package_label}/{object_meta.model_name}{self.template_name_suffix}.html" 

62 ) 

63 

64 return names 

65 

66 

67class DetailView(ObjectTemplateViewMixin, TemplateView): 

68 """ 

69 Render a "detail" view of an object. 

70 

71 By default this is a model instance looked up from `self.queryset`, but the 

72 view will support display of *any* object by overriding `self.get_object()`. 

73 """ 

74 

75 template_name_suffix = "_detail" 

76 

77 

78class CreateView(ObjectTemplateViewMixin, FormView): 

79 """ 

80 View for creating a new object, with a response rendered by a template. 

81 """ 

82 

83 def post(self) -> Response: 

84 """ 

85 Handle POST requests: instantiate a form instance with the passed 

86 POST variables and then check if it's valid. 

87 """ 

88 # Context expects self.object to exist 

89 self.load_object() 

90 return super().post() 

91 

92 def load_object(self) -> None: 

93 self.object = None 

94 

95 # TODO? would rather you have to specify this... 

96 def get_success_url(self): 

97 """Return the URL to redirect to after processing a valid form.""" 

98 if self.success_url: 

99 url = self.success_url.format(**self.object.__dict__) 

100 else: 

101 try: 

102 url = self.object.get_absolute_url() 

103 except AttributeError: 

104 raise ImproperlyConfigured( 

105 "No URL to redirect to. Either provide a url or define" 

106 " a get_absolute_url method on the Model." 

107 ) 

108 return url 

109 

110 def form_valid(self, form): 

111 """If the form is valid, save the associated model.""" 

112 self.object = form.save() 

113 return super().form_valid(form) 

114 

115 

116class UpdateView(ObjectTemplateViewMixin, FormView): 

117 """View for updating an object, with a response rendered by a template.""" 

118 

119 template_name_suffix = "_form" 

120 

121 def post(self) -> Response: 

122 """ 

123 Handle POST requests: instantiate a form instance with the passed 

124 POST variables and then check if it's valid. 

125 """ 

126 self.load_object() 

127 return super().post() 

128 

129 def get_success_url(self): 

130 """Return the URL to redirect to after processing a valid form.""" 

131 if self.success_url: 

132 url = self.success_url.format(**self.object.__dict__) 

133 else: 

134 try: 

135 url = self.object.get_absolute_url() 

136 except AttributeError: 

137 raise ImproperlyConfigured( 

138 "No URL to redirect to. Either provide a url or define" 

139 " a get_absolute_url method on the Model." 

140 ) 

141 return url 

142 

143 def form_valid(self, form): 

144 """If the form is valid, save the associated model.""" 

145 self.object = form.save() 

146 return super().form_valid(form) 

147 

148 def get_form_kwargs(self): 

149 """Return the keyword arguments for instantiating the form.""" 

150 kwargs = super().get_form_kwargs() 

151 kwargs.update({"instance": self.object}) 

152 return kwargs 

153 

154 

155class DeleteView(ObjectTemplateViewMixin, TemplateView): 

156 """ 

157 View for deleting an object retrieved with self.get_object(), with a 

158 response rendered by a template. 

159 """ 

160 

161 template_name_suffix = "_confirm_delete" 

162 

163 def get_form_kwargs(self): 

164 """Return the keyword arguments for instantiating the form.""" 

165 kwargs = super().get_form_kwargs() 

166 kwargs.update({"instance": self.object}) 

167 return kwargs 

168 

169 def get_success_url(self): 

170 if self.success_url: 

171 return self.success_url.format(**self.object.__dict__) 

172 else: 

173 raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.") 

174 

175 def post(self): 

176 self.load_object() 

177 self.object.delete() 

178 return ResponseRedirect(self.get_success_url()) 

179 

180 

181class ListView(TemplateView): 

182 """ 

183 Render some list of objects, set by `self.get_queryset()`, with a response 

184 rendered by a template. 

185 """ 

186 

187 template_name_suffix = "_list" 

188 context_object_name = "objects" 

189 

190 def get(self) -> Response: 

191 self.objects = self.get_objects() 

192 return super().get() 

193 

194 def get_objects(self): 

195 raise NotImplementedError( 

196 f"get_objects() is not implemented on {self.__class__.__name__}" 

197 ) 

198 

199 def get_template_context(self) -> dict: 

200 """Insert the single object into the context dict.""" 

201 context = super().get_template_context() # type: ignore 

202 context[self.context_object_name] = self.objects 

203 return context 

204 

205 def get_template_names(self) -> list[str]: 

206 """ 

207 Return a list of template names to be used for the request. May not be 

208 called if render_to_response() is overridden. Return the following list: 

209 

210 * the value of ``template_name`` on the view (if provided) 

211 object instance that the view is operating upon (if available) 

212 * ``<package_label>/<model_name><template_name_suffix>.html`` 

213 """ 

214 if self.template_name: # type: ignore 

215 return [self.template_name] # type: ignore 

216 

217 # If template_name isn't specified, it's not a problem -- 

218 # we just start with an empty list. 

219 names = [] 

220 

221 # The least-specific option is the default <app>/<model>_detail.html; 

222 # only use this if the object in question is a model. 

223 if hasattr(self.objects, "model") and hasattr(self.objects.model, "_meta"): 

224 object_meta = self.objects.model._meta 

225 names.append( 

226 f"{object_meta.package_label}/{object_meta.model_name}{self.template_name_suffix}.html" 

227 ) 

228 

229 return names