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
« 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
4from .forms import FormView
5from .templates import TemplateView
8class ObjectTemplateViewMixin:
9 context_object_name = ""
11 def get(self) -> Response:
12 self.load_object()
13 return self.render_template()
15 def load_object(self) -> None:
16 try:
17 self.object = self.get_object()
18 except ObjectDoesNotExist:
19 raise Http404
21 if not self.object:
22 # Also raise 404 if the object is None
23 raise Http404
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 )
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
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:
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
52 # If template_name isn't specified, it's not a problem --
53 # we just start with an empty list.
54 names = []
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 )
64 return names
67class DetailView(ObjectTemplateViewMixin, TemplateView):
68 """
69 Render a "detail" view of an object.
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 """
75 template_name_suffix = "_detail"
78class CreateView(ObjectTemplateViewMixin, FormView):
79 """
80 View for creating a new object, with a response rendered by a template.
81 """
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()
92 def load_object(self) -> None:
93 self.object = None
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
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)
116class UpdateView(ObjectTemplateViewMixin, FormView):
117 """View for updating an object, with a response rendered by a template."""
119 template_name_suffix = "_form"
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()
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
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)
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
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 """
161 template_name_suffix = "_confirm_delete"
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
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.")
175 def post(self):
176 self.load_object()
177 self.object.delete()
178 return ResponseRedirect(self.get_success_url())
181class ListView(TemplateView):
182 """
183 Render some list of objects, set by `self.get_queryset()`, with a response
184 rendered by a template.
185 """
187 template_name_suffix = "_list"
188 context_object_name = "objects"
190 def get(self) -> Response:
191 self.objects = self.get_objects()
192 return super().get()
194 def get_objects(self):
195 raise NotImplementedError(
196 f"get_objects() is not implemented on {self.__class__.__name__}"
197 )
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
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:
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
217 # If template_name isn't specified, it's not a problem --
218 # we just start with an empty list.
219 names = []
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 )
229 return names